{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "bLEEW13uCtiJ"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/jeffheaton/app_deep_learning/blob/main/t81_558_class_03_2_pytorch.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "5oX6K_-JCtiL"
   },
   "source": [
    "# T81-558: Applications of Deep Neural Networks\n",
    "\n",
    "**Module 3: Introduction to PyTorch**\n",
    "\n",
    "- Instructor: [Jeff Heaton](https://sites.wustl.edu/jeffheaton/), McKelvey School of Engineering, [Washington University in St. Louis](https://engineering.wustl.edu/Programs/Pages/default.aspx)\n",
    "- For more information visit the [class website](https://sites.wustl.edu/jeffheaton/t81-558/).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "FzlMdsudCtiL"
   },
   "source": [
    "# Module 3 Material\n",
    "\n",
    "- Part 3.1: Deep Learning and Neural Network Introduction [[Video]](https://www.youtube.com/watch?v=d-rU5IuFqLs&list=PLjy4p-07OYzuy_lHcRW8lPTLPTTOmUpmi) [[Notebook]](t81_558_class_03_1_neural_net.ipynb)\n",
    "- **Part 3.2: Introduction to PyTorch** [[Video]](https://www.youtube.com/watch?v=Pf-rrhMolm0&list=PLjy4p-07OYzuy_lHcRW8lPTLPTTOmUpmi) [[Notebook]](t81_558_class_03_2_pytorch.ipynb)\n",
    "- Part 3.3: Encoding a Feature Vector for PyTorch Deep Learning [[Video]](https://www.youtube.com/watch?v=7SGPm2tIT58&list=PLjy4p-07OYzuy_lHcRW8lPTLPTTOmUpmi) [[Notebook]](t81_558_class_03_3_feature_encode.ipynb)\n",
    "- Part 3.4: Early Stopping and Network Persistence [[Video]](https://www.youtube.com/watch?v=lS0vvIWiahU&list=PLjy4p-07OYzuy_lHcRW8lPTLPTTOmUpmi) [[Notebook]](t81_558_class_03_4_early_stop.ipynb)\n",
    "- Part 3.5: Sequences vs Classes in PyTorch [[Video]](https://www.youtube.com/watch?v=NOu8jMZx3LY&list=PLjy4p-07OYzuy_lHcRW8lPTLPTTOmUpmi) [[Notebook]](t81_558_class_03_5_pytorch_class_sequence.ipynb)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "CpVJpj2DCtiM"
   },
   "source": [
    "# Google CoLab Instructions\n",
    "\n",
    "The following code ensures that Google CoLab is running and maps Google Drive if needed.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "wU1AMzx8CtiM",
    "outputId": "014519db-db87-41b6-f5b6-e0a68e2fdffe"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: not using Google CoLab\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    import google.colab\n",
    "\n",
    "    COLAB = True\n",
    "    print(\"Note: using Google CoLab\")\n",
    "except:\n",
    "    print(\"Note: not using Google CoLab\")\n",
    "    COLAB = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1Zg-zNx0CtiN"
   },
   "source": [
    "# Part 3.2: Introduction to PyTorch\n",
    "\n",
    "PyTorch[[Cite:paszke2019pytorch]](https://arxiv.org/abs/1912.01703) is an open-source software library for machine learning in various kinds of perceptual and language understanding tasks. It is currently used for research and production by different teams at [Meta Platforms](https://about.facebook.com/). Companies have built several pieces of deep learning software on top of PyTorch, including Tesla Autopilot, Uber's Pyro, Hugging Face's Transformers, PyTorch Lightning, and Catalyst. PyTorch provides two high-level features: NumPy-like tensor computing and deep neural networks.\n",
    "\n",
    "- [PyTorch Homepage](https://pytorch.org/)\n",
    "- [PyTorch GitHib](https://github.com/pytorch/pytorch)\n",
    "- [PyTorch Forums](https://discuss.pytorch.org/)\n",
    "\n",
    "## Why PyTorch\n",
    "\n",
    "- Supported by Meta\n",
    "- Works well on Windows, Linux, and Mac\n",
    "- Excellent GPU support\n",
    "- Python is an easy to learn programming language\n",
    "- Python is extremely popular in the data science community\n",
    "\n",
    "## Deep Learning Tools\n",
    "\n",
    "PyTorch is not the only game in town. The biggest competitor to PyTorch is TensorFlow/Keras. Listed below are some of the deep learning toolkits actively being supported:\n",
    "\n",
    "- **[TensorFlow](https://www.tensorflow.org/)** - Google's deep learning API. The focus of this class, along with Keras.\n",
    "- **[Keras](https://keras.io/)** - Acts as a higher-level to Tensorflow.\n",
    "- **[PyTorch](https://pytorch.org/)** - PyTorch is an open-source machine learning library based on the Torch library, used for computer vision and natural language applications processing. Facebook's AI Research lab primarily develops PyTorch.\n",
    "\n",
    "Other deep learning tools:\n",
    "\n",
    "- **[Deeplearning4J](http://deeplearning4j.org/)** - Java-based. Supports all major platforms. GPU support in Java!\n",
    "- **[H2O](http://www.h2o.ai/)** - Java-based.\n",
    "\n",
    "In my opinion, the two primary Python libraries for deep learning are PyTorch and Keras. Generally, PyTorch requires more lines of code to perform the deep learning applications presented in this course. This trait of PyTorch gives Keras an easier learning curve than PyTorch. However, if you are creating entirely new neural network structures in a research setting, PyTorch can make for easier access to some of the low-level internals of deep learning.\n",
    "\n",
    "## Using PyTorch Directly\n",
    "\n",
    "PyTorch is also a low-level mathematics API, similar to [NumPy](http://www.numpy.org/). However, unlike Numpy, PyTorch is built for deep learning. PyTorch compiles these compute graphs into highly efficient C++/[CUDA](https://en.wikipedia.org/wiki/CUDA) code.\n",
    "\n",
    "## PyTorch Linear Algebra Examples\n",
    "\n",
    "PyTorch is a library for linear algebra, other components of PyTorch are a higher-level abstraction for neural networks that you build upon the lower level linear algebra.\n",
    "\n",
    "PyTorch can compute on a GPU, CPU, or other advanced compute device. If you are using a Mac, PyTorch is now adding additional support for Apple silicone (M1, M2, M3, etc). For apple support we will use Metal Performance Shaders (MPS). For this course, we assume you will utilize a GPU (cuda), CPU, or MPS. The following code detects the available device and defines the **device** variable that the following code will use for computation. For parts of this course that I know do not work for MPS, we will fall back to CPU. [CUDA](https://en.wikipedia.org/wiki/CUDA) is an NVIDIA standard for accessing GPU capabilities.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "istrQO-2-1DK",
    "outputId": "3de2f86d-f197-4659-8e26-0449d74badf7"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using device: mps\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "\n",
    "# Make use of a GPU or MPS (Apple) if one is available.\n",
    "device = (\n",
    "    \"mps\"\n",
    "    if getattr(torch, \"has_mps\", False)\n",
    "    else \"cuda\"\n",
    "    if torch.cuda.is_available()\n",
    "    else \"cpu\"\n",
    ")\n",
    "print(f\"Using device: {device}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "nsr6Or3oHTDu"
   },
   "source": [
    "Now that we defined a compute device, I will demonstrate some basic linear algebra that directly employs PyTorch and does not implement a neural network. We begin with a the multiplication of a row and column matrix.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "bkh9pHqPCtiO",
    "outputId": "d09d973b-4e80-4841-9f41-e20232eb520c"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[12.]], device='mps:0')\n",
      "12.0\n"
     ]
    }
   ],
   "source": [
    "# Create a Constant op that produces a 1x2 matrix.  The op is\n",
    "# added as a node to the default graph.\n",
    "#\n",
    "# The value returned by the constructor represents the output\n",
    "# of the Constant op.\n",
    "matrix1 = torch.tensor([[3.0, 3.0]], device=device)\n",
    "\n",
    "# Create another Constant that produces a 2x1 matrix.\n",
    "matrix2 = torch.tensor([[2.0], [2.0]], device=device)\n",
    "\n",
    "# Create a Matmul op that takes 'matrix1' and 'matrix2' as inputs.\n",
    "# The returned value, 'product', represents the result of the matrix\n",
    "# multiplication.\n",
    "product = torch.mm(matrix1, matrix2)\n",
    "\n",
    "print(product)\n",
    "print(float(product))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "pMrXqwskCtiP"
   },
   "source": [
    "This example multiplied two PyTorch tensors. You can see that we created each tensor on the appropriate device, either the GPU or CPU. Next, we will see how to subtract a constant from a variable.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "qKyFCaoTCtiP",
    "outputId": "a1234aec-3533-4d18-cb16-2ea2314877de"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([-2., -1.], device='mps:0')\n",
      "[-2. -1.]\n"
     ]
    }
   ],
   "source": [
    "x = torch.tensor([1.0, 2.0], device=device)\n",
    "a = torch.tensor([3.0, 3.0], device=device)\n",
    "\n",
    "# Add an op to subtract 'a' from 'x'.  Run it and print the result\n",
    "sub = torch.subtract(x, a)\n",
    "print(sub)\n",
    "# Use cpu() in case tensor was on GPU.\n",
    "print(sub.cpu().numpy())\n",
    "# ==> [-2. -1.]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "epQ9LrM6CtiP"
   },
   "source": [
    "Of course, variables are only helpful if we can change their values. The program can accomplish this change in value by calling the assign function. To use Numpy, we must first bring the tensor back to the CPU with the **cpu()** command. Next, we call **numpy()** to access the tensor as a Numpy array. If we were already on the CPU, this function has no effect and returns the already CPU-resident tensor.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "id": "vtnW5aU-CtiP"
   },
   "outputs": [],
   "source": [
    "x[0] = 4.0\n",
    "x[1] = 6.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zHwJP8MjCtiQ"
   },
   "source": [
    "The program can now perform the subtraction with this new value.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "0e26Fe-GCtiQ",
    "outputId": "98777e96-67c5-4a60-ff5b-fbffd9be4dda"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1., 3.], device='mps:0')\n",
      "[1. 3.]\n"
     ]
    }
   ],
   "source": [
    "sub = torch.subtract(x, a)\n",
    "print(sub)\n",
    "print(sub.cpu().numpy())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Rq61YFBYCtiQ"
   },
   "source": [
    "In the next section, we will see a PyTorch example that has nothing to do with neural networks.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "iMVIS9pBCtiQ"
   },
   "source": [
    "## PyTorch Mandelbrot Set Example\n",
    "\n",
    "Next, we examine another example where we use PyTorch directly. To demonstrate that PyTorch is mathematical and does not only provide neural networks, we will also first use it for a non-machine learning rendering task. The code presented here can render a [Mandelbrot set](https://en.wikipedia.org/wiki/Mandelbrot_set).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "id": "9rEFmqbXsfME"
   },
   "outputs": [],
   "source": [
    "from io import BytesIO\n",
    "\n",
    "import numpy as np\n",
    "import PIL.Image\n",
    "from IPython.display import Image\n",
    "\n",
    "\n",
    "# Take a NumPy array and render it as a Mandelbrot.\n",
    "def render(a):\n",
    "    a_cyclic = (a * 0.3).reshape(list(a.shape) + [1])\n",
    "    img = np.concatenate(\n",
    "        [\n",
    "            10 + 20 * np.cos(a_cyclic),\n",
    "            30 + 50 * np.sin(a_cyclic),\n",
    "            155 - 80 * np.cos(a_cyclic),\n",
    "        ],\n",
    "        2,\n",
    "    )\n",
    "    img[a == a.max()] = 0\n",
    "    a = img\n",
    "    a = np.uint8(np.clip(a, 0, 255))\n",
    "    f = BytesIO()\n",
    "    return PIL.Image.fromarray(a)\n",
    "\n",
    "\n",
    "# Loop through the render cycles for. Mandlebrot plot.\n",
    "def mandelbrot_helper(grid_c, current_values, counts, cycles):\n",
    "    for i in range(cycles):\n",
    "        # The Mandlebrot formula\n",
    "        temp = current_values * current_values + grid_c\n",
    "        not_diverged = torch.abs(temp) < 4\n",
    "        current_values.copy_(temp)\n",
    "        counts.copy_(torch.add(counts, not_diverged.double()))\n",
    "\n",
    "\n",
    "# Render a Mandelbrot plot at the specified location, zoom, and render cycles.\n",
    "def mandelbrot(render_size, center, zoom, cycles):\n",
    "    f = zoom / render_size[0]\n",
    "\n",
    "    real_start = center[1] - (render_size[1] / 2) * f\n",
    "    real_end = real_start + render_size[1] * f\n",
    "    imag_start = center[0] - (render_size[0] / 2) * f\n",
    "    imag_end = imag_start + render_size[0] * f\n",
    "\n",
    "    real_range = torch.arange(\n",
    "        real_start, real_end, f, dtype=torch.float32, device=device\n",
    "    )\n",
    "    imag_range = torch.arange(\n",
    "        imag_start, imag_end, f, dtype=torch.float32, device=device\n",
    "    )\n",
    "    real, imag = torch.meshgrid(real_range, imag_range, indexing=\"ij\")\n",
    "    grid_c = torch.complex(imag, real)\n",
    "    current_values = torch.clone(grid_c)\n",
    "    counts = torch.Tensor(torch.zeros_like(grid_c, dtype=torch.float32))\n",
    "\n",
    "    mandelbrot_helper(grid_c, current_values, counts, cycles)\n",
    "    return counts.cpu().numpy()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "O77aOTEDrWRU"
   },
   "source": [
    "With the above code defined, we can now calculate and render a Mandlebrot plot.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 515
    },
    "id": "t3ZfV8A_p_5j",
    "outputId": "02c7b806-42a1-4e97-ad7e-1df103152156"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(640, 480)\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAIAAAC6s0uzAAC+kElEQVR4nOydf3xT9b3/X0l6ctomza82SQk9pQiIYmkFdDJAmDKwCrTX7eqmu7vfe6fzF/7Yvdtk6jZ1mzrdT3/gj0233bsNrzjdLaDW9jKGQsWpIKWiCEjpKaE5aX4nbU9Ok3z/OGmapmmatGnz6/N85KE0OT8+aZPzPO/P5/15fyTV1RtASBMhlSzTTSAQCITpReIOZLoJeYI00w3IH4h9CQRCIUCudemCCDg9kE8kgUAoHMgVLy0QAacB8lkkEAiFBrnuTZ2iTDcgtyEfQQKBULCIF0AyJDxpSAQ8eYh9CQQCgVwJJw0R8CQhnzkCgUAQIdfDyUEEPBnIp41AIBCiIVfFSUDGgFODfMgIBAIhLmRIOFVIBJwCxL4EAoGQGHKdTB4i4GQhnyoCgUBIBnK1TBIi4KQgnycCgUBIHnLNTAYi4IkhnyQCgUBIFXLlnBAi4AkgnyECgUCYHOT6mRiSBT0u5KNDIBAIU4SkRieARMDxIfYlEAiEdEGuqHEhAo4D+awQCARCeiHX1bEQAcdCPiUEAoEwHZCrawxEwKMgnw8CgUCYPsg1NhqShDUC+WQQCDEEVKXpPaDM3Z/eAxJyjpBKRnKyRIiAwxD7EgqWtFt2cucibi4ciINFiIABYl9CwTCTrk2VsW0jSs5jiINBBAxiX0Jek83GnZCYxhMf5xnEwYUuYGJfQp6R08ZNTPRbIzLODwrcwQUtYGJfQn6Qx9IdDyLjvKGQHVy4Aib2JeQ0BSjd8SAyznUK1sEFKmBiX0KOQrybmMjvh5g4tyhMBxeigIl9CbkFke4kICbOOQrQwQUnYGJfQq5AvJsWiIlziEJzcGEJmNiXkP0Q704T4i+WaDjLKSgHF5CAiX0J2Qzx7sxAAuLsp3AcXCgCJvYlZC1EvRmBBMTZTIE4uCAETOxLyEKId7MBouGspRAcnP8CJvYlZBXEu1kI6ZfOTvLewXkuYGJfQvZA1Jv9kIA428hvB+ezgIl9CdkA8W7OQTScVeSxg/NWwMS+hIxD1JvTEA1nD/nqYGmmGzAtEPsSMktAVUrsmx+Qv2OWkJdX9TyMgPPy70TIFcj1Ov8goXCWkH9xcL4JmNiXkBGId/MeouFsIM8cnFcCJvYlzDxEvQUF0XDGyScH588YMLEvYYYhA70FC/m7Z5a8udrnSQScN38PQk5Arr8EEgpnlvyIg/NEwATCzEDUS4iGaJgwFfKhC5qEv4QZgHQ4E8aDfDAyQh5c+XM+As6DvwEhyyGXV8KEkFA4I+R6R3RuC5jYlzCtEPUmz5BCm9L2RT7HNLUkgwRUpcTBM0xOOziHBUzsS5g+iHonJFXjJt49b3xMQuGZJ3cdnKsCJvYlTBNEvYmZoncnPGx+mJiEwjNMjjo4JwVM7EuYDoh6EzBN3k1wolw3MXHwDJOLDs5JARMIaYfYNy4z5t3xTp3TGibd0YTE5J6ASfhLSC9EvXHJoHqjyQ8NEwfPDDkXBOeYgIl9CWmEqDcuWaLeaHJdw8TBM0ZuOTiXBEzsS0gXRL1xyUL1RpPTGibd0TNGDjk4ZyphEfsS0gWx71iGFNost2+EHGrqWMhnb2bIFV/kUgRMIEwRcvmLS7p8JtD6mGco3pqWI49lSKHN3VCYxMEEkdwQcK7czhCyFqLeuKRFvWO9G/eltMs4d3ukiYNngJzoiM4BARP7EqYCUW9cplu94208HRomDibEJfsdnO0CJvYlTAVi37hM3b4pqXfsjunVcI6GwiQtawbIcgdntYCJfQmThqg3LtMR+PplxribyQOWCY+QRhOTUJgQl2x2cFYLmECYBES94zGt9vWHKsV/yCW9MS9hfBmnNyDO3VCYOLgwyV4Bk/CXMAmIfeMyTXnOomJ9/jr06wDg4wos+xTUiINjtpwxDRMHE6LJ2iA4SwVM7EtIFaLe8Zi+WUaIBL5eFVg51BIAgmAAFbWXYABXQ83+h1zS65cZJ+yXTouGiYMJMWSng7NRwMS+hFQh9h2P6a5ZIZf0goJgAJQ67LVBNRdKtxD91+jXgZULBoNc3gsgsYORPg0TBxNiyEIH50wlLAIhLgFVKbFvXNJbMSpx2jNFcSi1Y9lcwwcPacq248RC9OvQrwNXgwNlYPyIGiQeL2kr5nTiYyptzsWaWeTDXFBIqqs3ZLoNoyDhLyFJyKUqAekVT/zO5yiP+kOVgmAAAK4GbhrHrTBUQC1B9yCqi2GyotROUVz02HDiODiGKQbEORcKkzh4+siqIDi7ImBiX0KSEPsmICNhH0VxAKB0w2TFMhVcvOL4dqhpqHiU2sVtIkEwkouDI0w9FJ7K7jMP+XhPH1llmewSMIEwIaTPOQHT0emamvyGXYvqYh/qoJZA6R5v21QdPBUNEwcTspAsEnBW3ZgQshNyVUrAdDgmJeeFg+BSuyhdBTrGbhMdBAPwy4wzpmHiYIJI9rgmWwScPb8RQnZCAt/EZKFd1NJ9cIXgVYmzhMODxGMcjElpeHJNyrm0LPKZnyayxDhZIeAs+V0QshZyGUrA9ElliiOv5uAPRn6YyMEY1nCSJi6c7mjy4Z8mssE7WSFgAmE8SOCbmCx1iVeF7kEF9gCAm4ZXBa8KXA0mcnD4pWETJ1Zy4XRHk69AvpJ5AWfDbQghCyHqTcx096ZOKfx101DTPtSB60P3IFg5WLn2g+eSiYPjksDExMGESZNx+2RYwBl//4TshFxuEjPd8kjVarHFn01WMH7QNQY8aRLupri3DNz9DvomeFUxO/pDlclrGOMkThMHEyZNZh2U+QiYQIiGBL4Tkg3aSHKkltaYzcH/EXANAKhpIHYwOHy0cTQsPu8PVfr8dT5/nbhN2h2cDb/PJCFfjTwjk7WgSfhLiIFcXxIzM6qYYu5VGDcNDPJO08gzLh7qYkAFAKV2QTCEpy0NM14oLAgG9OvwcYWwDEkWlE6VHCocTepFp50M1ojOWARM7EuIhgS+E5JL9vWqqON7Tcu2+DcaFU1PM4sbBVSA74IrBFau3ftiJA6OCYXjtEfcwKsC14eEw8aFUy2LfFPSTqZ8lI2rIREKDXJBmZCs0sPYTuCxXtRit/nEr+HiFfwOFo8AwBoJ9h4zSZ82Gx6H1w+Ey2ZFHBwdEI8SM1eDzhAW6AF75Lm4QbBA66dSNZrEwYQZJjMCJuEvQYSod0JmUr1TD38pihMEA5Rubtl9gB/qYh+uhSsEAHv7GM3P2YGnxGWDAaBfN1K6cszAcHgDEb4LOA/9OqEUoMbkfI1+CwXiYEJ6yUhHdAa6oIl9CSLEvhOShfZNKv1KrEapdEPFQ8WHjUuXsc5vg/eEtxEzoiOKFf8d8xA3c9MAmOIbYdaLk4kTt6RAFjEkX5+0M/NuIlnQhAxARnyTIQvtmzLiSgzDDqbAATZ0D1IfHACiHBzRrVlv2PtkuHCHWQ+zHqycOr7XJH2aHXweAD5gpqWdYyAOJswAM90FTcJfArlqTMgMX/2Tt28KFZtL7aOiWwCAFrsFVFB8H4c74Kah4sfODKY1ZrByuHjG+O+s5RnwXQIWmeetgckKAPPfBjhMxBQ7okVyojuaDAanlxnuiJ7RCJjYt8AhgW8y5ETsFc0ElTQiyxGqaQ7XK+qOYJ2UwlEct4p9y2HcNNw0XKFe59cp7i3m3NvZ7tdMwt2ajbspHIWKB4BSe+zMpfFvCNIS0+fE34J8p9LLTHqKZEETZghymZiQjFzupyX8FYkOglU8XHI0SH0tiwEocdiBRXCFABoAXCEgBBcP3qPEYS/q2SPfUTQ1WJv1wj9Wg+bxwSlcKRn/TNOI+EfJ8lCYxME5ysxFwCT8LVhI4JsM+WbfuLRoHbjOgfXKxR1avAgXD1cIrhC4PgN3Py7rNuBJL+oF+hLQNb6WjVrshlqCWgnWlGP0PKVkSOPAdk6EwoR0MWO2IhEwYXoh6p2QTF3c0+KnpCo5K93wqqCWABVw8eABFt7GpdSOdwVuEQAKR+nFZkrOARBghJoWk7Y41X1QWgFEz1bKFFk+JEyC4FxkhgRMwt8ChKg3GXLCvukJfwGoJQANFwAodxyk1tl9bRYFOmnNmd4j/4ojAQ53gC6DWhIe9BXHj0vtGD/8TVyWMi3ZWBGIgwuHmcnGklRXb5jucxD7FiDEvhOSwV7NdNk3bvg7qp5GZAxYzHZ20+GiHFwfYNOilVpnF9p0XtQLWAS6LBz7jrEvogQ8tgrHhHWh0+hgZP14MHFwGpluB5N5wIQ0Q0Z8kyEP7Bv/4HHtG81wUQ7xJ7q3J9a+IpHc6bFNGmP9tAXoyZErlToI2c+0C5iEvwUFUe+EZPDyLdD6NOYlpbCOb/RkXzECBgA4Ghrs5zQIMIbtKyKGv2llOsqMZK2DyXcwjUy3v0gETEgb5Js/IbkS+IpMqfN5LJFZvy4efBcFC3PmMfXOtyhYwvUp1ZOfaDRhEEwcTMhCplfAJPwtEEi384Rktt8yvfaNf4oY+44d/cVw+Mt7tGit1PyRr6ySqfq12M1U3w1X+gPfGYA4OO+ZVotNo4CJfQsE8lVPTMaHDNNu37Hhbwr25fpM0h97G5fyF1dxbU+zA0/R1SzbvWVkg2SaFy/+zkgQjCz4+xKmm+lzGemCJkweEvhOSMbVmxX2FattAGKYaw1+WbnjIP1eD3CEMd7KdzOmFc+M7C7aekyN6LQwXWtOZPoPHRfy3cx+pmseMAl/8x7y9U5Mxq/IaZfNxOpFfPsquJf9MApYFL0h63wYDS4+MMfXvcjXXgda1DMNtSS8TsOYc0VPBfaHKsfOR0o8Jzh8nLTODI4mC2cJk5nB6WKapgWTCJiQMiTwTUzG+ySnku08Xvibsn3dNFg5XCEx6tXL/kLhKGBTYI9e+kqJ9IRpxUNAqGi3XY7ekTwsF4/uQcZ6I9x0nAWDJ2pSxsn4XRcht5iWCJiEv3kMUW9iMn4Jnkrgm6R9E6kX4dhXe/wJsciGH5Vy9PYFvqzEYT8q1dJ95vW3U3JOOLpK3v53rJCUSD7z768UYARvo2CprP6Dt3sJ5CGM6YSOCYIRLw7ObBCM7Fu8gQTB6WI6gmBSC5qQLES9iclp9SJV+44XmA73PDsMd1Btb+mlr/g3mUqsJx39txrt/zV4Xo3sH/2GwDZhh7ZU9j9n5j1lHlhhqHhYgU5xb2qjFadlDlw3akpSvy5SD2usgyfHtDoYWdYdTRyctaS/FCUJf/MSYt8EZFy9yIB9JTBXABgpGxmdNhVOvArP91XiMLXOLm0bLKqzOeZeIWke8s77Kk4aAJhWXm8t/+fKt/4AVSDQU9Z/1UJhl96Hy2CoCNekjJTEGr0eQ7SGJ1GcMnyQ6XQwsikOJgJOF+kNgskYMGECyIhvAjI+3It01LdK2b5eFQ4oTfq70D0YJ2k5UnADAMoFGP2oLNptD64r7pFtLR046sWdon0BmI/9SthRbl/T4Km/2By8tWzfId+ay5nquw3c/bGtGR1zC4Ih0hM+6eKU05cULZLxz0YE8hXOTtIcAZPwN88g39sEZPzymp71BJOwb2yFZ68KnSHwbsAOAIaFsUWsIpOOeI+46IJS0+FZtUTJfthzuHnMqY6UNu3v52sNgW3iwgwABPoSVBePWpUhLqX28RZpSDIIBomDCSmSxiA4nREwsW8+QQLfBGRJ4Dv1g6Rm335d2L5uGoACf2c0Py9qtFHcW6NqaIyyLyhYSlacdDjXSnf5ew7/JN7ZFvc3X6KgO4p22/2oFMQJS2Jp6Eh4LT6i8apwoAxczXj1L5Ov5FU4cTAhLaTRdKQLmhAHot7xyBL1Tqt9R50r0u0s9gAP2xeAD3UBd6lh10sCFsHFh6ttiEO/Lh58F2ADbHJYcCCkXbwbkACL459miV639w1zzRPhbCxxrSRR5NEd2l4VzPrwf1m5gX941NuZwipJM+DgjH9sQL7X2UfaBEzC3/yABL7jkQ3X0DQuZ5TMQguCYEC/DlwN9tpg1sOsZ6y3VJV8E8etYnYVAHPw+xSOgveEvcv1UVe8b+AfNkmfxvzzFOiQo3coqAHgxYXjtuajgH3NlabKuwRUAAgfDVEOFh+sXHf814YPHhI1T2vMjPUW9OsmWAQiOabbwciOUJh8u9NCunxHImDCCOTLGZc8Uy9Ssq9Zjw/cBmxD9yC6B3u7/03SHaIaj5lkW7Xr9siqPAY8Gd6T94RHfHe0Yp1UWjvA9N2oQKdycUfRClegTOFvMBlxU9yTKv3NVMCCAyE/KilYKBzV8r8ZcXBUbO3BEnqxWcs9aVj9017n11n9s3Ebn8w7HUuBOJiQPaRHwCT8zXVI4BuXbFAv0i2GFJz0cQVTfCMAJy6n+HeLrvigUvNHr7NOETw8EFhAHzvNL5wDSACIVa4AGwWLD7VFu+0BldK+poFebLaf0yA74za33zf7/57woZbRboo5SWnTc975Xy21fyqr8ijQqcRhvfQVf5MJfNeoJZJcPMW/KyxdDlfA32Ti2jYL9CVA7PSkKb3fdN/oxCXjnyjyTU8LabEeKcRBIF/IOGT8KimS/nrOKS20cH4f692KBbT/+AVU47FZb/0egAPrsQsULEp1h2zAJZEGlMHDflTqZX8ZCCwAQKFvMDjXLv1XU9+P4Q74+HpZvRtsyZmh7cB2b3RG8KXHjG//woOQ6dQdfSe+HGosEuaeZ3r7R7Iqj6/9y4qmV3zNgKsmvDHfpZe+Yj54vhdLfN1rwhOFldboMh3pIu/LdJDSHFlCGqYhkfA3dyHqHUu+qhdJxIJxkp/FtGdWTnFvhSNOALyHwlF5k9V35J8Vi/9CBSylrx+TXdDPzr6bOfAYAP7iKntxw9DORiNusqz6DvadO2HbynF3cVGXdeifQ41FQs/FVUO3+zoWO7AeKB/exMYs/rn9nAY0B32oA10TnqekdIsCHm8+UvjJpGclRTOtGs6sg4mA08IUpyQRARcuxL4x5LF6kaJ9EVNyUkx+jsw1cvHgu7RodWCdEodL130CgGu7DbTKtGyLS3+pr3l1Vd19PR1jp/xOwGzZtYGA0tm0RopBZfMhH2oB+FAHgIJFL31laK3O2faFihV/tbZ/SaAvQa0kSQGDOHgMxMFTZ4oCnuoYMLFvLkJGfMeSx/b1y4wpjYOOItK7q+KhlkQKblCwUOjTbPyb9pw3ube+52z7AlOzBbzL3H6Lr2UjUDsJ+wI4E9jei9/p39kuQYDWmNXSfWiSAiEFOpQ4LGZTV0hfHWpXKzZ2gu8CK8fHFQlWTIpmcr+EaR0VzpJPHWHSTNGAJAu64CDqjSF7Mq0y0u0c3izx0n5iLSqxLtWwg7ml9zmH1rOfPQpBkDW52a7XgMWoOE/OvzO1JiPE0Crbfs+qJVgu8Tm/wCz+hVq6r2TFSXqxWWjTDgTnO7BW2KU3SZ/Wck+OStSa6L1M+kZk+jScwc8euRRknCklYZHwN7cg37cYssG7IhlU72RQ00rusP+gRadp5TGr6PMec/O28Et9Rv+UD9/zwasAsJTF7Be0O//sQ23ROqej5ItC+5eY6kBv978JWCRgEYLw0Y3hhYSTXigpmfUKx0P8M6W9UzqDOVkkG2vqTGWZQpIFXSgQ+0bIHu8icyO+ozZOZmX70esdOXAdYPNVXGvy3GF+7zFgW4JdJ0fV0O2SziEWr0nRGpQq5TtOG9bd5m3TCTCCLoOa9qmvhYqHMs6+Y9cJHvXqFByM6dFwxvOiCRlh8l3QJPzNFciIb4Qs6W0Wmb4+56nbd+LaUnQZAPmJv8tme3E5C0CC3Sm0MgmsHdeyp14DEMR6vLnCr79caNM65t+JBinUdHixQpF0T0NKhrT/+TL1ySQXh6kzaRuSMeA8h3y7RLJKvciaZKukYt8Ywgv9igstcIGeMpN7K7CXwsTdvynB42uRf5tkXzFc+CAFm+LEduypHtlo/IWSEr+1KSWmRZFeDWfVR5QwA5Au6LyFqFck2y5qOTbcG0Hsf3aFEIRJuNuFVQDk6JVVedj2u4FyP9ZM05kZ7SZW8jhV0q5Fqw+XAYhd/XCyTLEvOkIaO6Uz0hdNRoIzxSQjYNL/nOUQ+yL7ol5MQ1HJqURy48WI4/Y/u2kATOk3sFziu/BqBTop9AHQorW46f3JtWFC9Os3917674zyBu2OVgfWhp+NXv1wfJKJ79N47zIDZSwJWcvknEgi4HyDqBfZF/UireqdujNS6HmOmWLr4u3clT5LIxiKrmYBsN3fwRqjfuDpwSm2aRysrVux9Hvs3PO03WKelw28Da4agIYq8a7ARNlY4W3SFAeLTD0aJkFw4TAZAZPwN2sh9s1C9SJN9k1XrJbYvvHD30j/M9+lXtmuc7eyup96yi6S81ZIL8Dej6xzfwg8lZbmxeEgY1px3RDUShwuWXESwED7PIHTe9XXxaRAx52JNPMOxpQ1TJKic5FJzEciSVh5Akl1zsIOZ0y5WzLSyZxJ+4q4aS33JKP5OUIh/ohJu/fPJc6T3FvfQ1cvcBlOpWFF3vGoqmsyt99Ha8zK6kPmwcdkR31KTYcXX0z+CEn2Rad9KH0qf/2Z/zAX+AUkI6QsYBL+ZiEF/s3JTvViCoFveqUbPmaoMmX7ju5/duA6/uIqV/tKB9Yqqz8sYh2qK14E6tLYyLh45ywBpeQvruIXzsFHocFL5rDO78BQARUfDs2j2jnePUSSve5ZpWHi4JwjVT+SCDi3KfDAN2vVi0nZd/q8O6F+Jp74C2CBnjt8v49uFLDI270EIbj9K6uKv5Seho6Pc+cDpVe1cm1foY+fZs69vfjjzyhMprs4+ZHvadJweg9IyANSWw2JhL9ZRYGrN9NNGJfJqTddZ5/E1N749o0Of8VAM7IgkosH36VAh07T6lm1RLJryIGHJtncJKk/w7C32C+9Es1BAGiS+o7+c7gQhzgVeHQtjsQ1KSccEh61cVrHhicxKjzzg8EkG2uKJD8STLKgcxKi3qwlVftOTr2TKaARj3ED37H2FVFLRAcr0IEmqSewRM5b+7EgLY0Zn70wF2G2BM1BHy4DXYZ3aKjHNDjKweL7Gk/DyaRljWwc9QeauownkZxFErLymBQETMLfLKFg7Zvl6sV02jddxo2QqM85idX9/DAqgh8537kNFx834r9lm37s2vmDdLYvCqbmMZTJAh+V+mEEEK5DmQQJlmcQf58phcJI8W4pga0FWp/NDiZTkqZI8unQJALOJYh6s5bpUG/apSsywXBvcmvrAhCkBlxgh6XGtmnj0M7GNLQsLhUWtsuiaNrn6/2y3LqXwlEBq+NvOToIDjdyolAYqWs4SSJ/4rgmTtXBhLyEJGHlDIVp32xOs4qQLvtGEqbGS5sSBEPMI7V2Jt6lXxfHvt741S70K/+qtr5teOvxkuqWWW//VrPpgZRakgJ9RkZzn9BcAbWEAifQlyRq4Th3D4l/XdNxoxN9zPFSulL62Mzwt6AwrzYzT7JJWKT/OYMU5pch+70rMnX7JiOAEXlwNQCgdCeZdjSxpBOEvNFuc9Phf4wsxmCjYNFLX7FuvFZ48yLw1XGPkQ6OABJGc4/deYWPbhzpgo6shjR2SYaJ1keK++uaeigs/ikjv3OK4qKPGTcUTikOnsmOaNILPUWS6YUmXdDZTgHaN1/VizH2jVZvUuFsvw6snDF+k/VuDT8zbJrUouEJ+5ljAt9o+4rQZeABWLBcYmjZJp31Ans6hfMniWbTA073tZqyl4Rdetb5XaA8tlWig8XWRmtYfIMxGo56Mm7XdOTPMTkTj/w1uRrtB885FtwpmHRisph4wLj1trK2L5qMBM8ASUXAJPzNCIWm3lzxrki67Dsizn4dvKqxoe3IqwDMenQPavnflKw4abY+ju5BLPcASS+IG9e743QyjxCxLzAyDQkQg2CsqTAJP+4vXziwcx6PryfVjBTo0OIlx/y7FCe2h/OfAahpIGpBpEgcjESrEwKAVwU3DZMVGPmNJZiwlEKmdMyNVL8OXhX1wQHBsBrLPhVPETnaWAeTIDhfmTAIJhFwllJQ9s0t9SJ9RRUEwYATC+EKgfHDTeP4Jwp0+NZcFn9rrwrHrczie+znNPia6wz4oQNrBe9yKN1x849iEe07VreiX6M1FvOSSCT2HbEvACj2/m0A88qq3xuCtqzhW30tv56gGUljwM2OxhPKt45QJ37I4fpxt4tu/9hQOIJo3+Mfo3suaiXASCicOEUL45s4qcHjfp0AHQCoxz1OSkEwmZWUT5AIOOsg6s1yJmff8cJfk+tpa8U12h2tAHyo9RmuAeOPbGb44CFuwY8AQMWH62Bwx5jqR+3dV/pwGQwVYPwjvhnPwZHA16tCZ0jL/0a5uIO1/Db8JNdnkv3IXP7EuHN7otf+cw17OixgGwWLHBYFOrk1d+r2/tHd+LmhHVcn9yuZiAoLLjqubWnxol6AMdz/HB0EY8zCwHFvI0TcNLoHoaLl1r1+ejlqJTGdDYlrdyRJuD9D7Mxg5QruZZ/hGqgljPXG3tX/L0EQjFTiYBIE5woTRsATZ0ET+84kxL7ZzKSL+iaYdGSmvi98vMqBtXQ160MdXDxYOdy0+KA1Zu3xJ9A9CFaO7kFwfQZsg1qmXrEfdFlYP5GgVkxjHvsQETdT04q6I3AFKO4tzeeeBnfMJP1x/5XngesbLdoQXCF0D6J7EC5+5CEybN+R3wwqTG//qHhF19COq4Ejk/gVxVCM/2JKv2Fo+VPUc7aRU0daMtzO2aduR/dg+Pc2luEaXgrryxXSV8PPeFXRv59UU8rHEnsEtcRHN5rmf4ux3sjO2yoIhnSlW+fcF6dgmdCeZBpStlBQVZ1zYnJRDFNZWWGCLboHAbDdjxnW/Y+C32Ga/y10D4peYZ3fKVlxErwHXJ+W/w1TfTeHZ9ne3w60zxt1hAnHcUXcNICejm976i7SrtvjHFpnWvGMTOWT7AoANrj4sM9EE3N9KKMN/MNa/jdy/gB4z/CjC7CJsa8Y/soRDuxkPW7TiusAO5b0pPY7Gk1p07P6xa/AHb54yWEZLv5sA2zgu8B7Rt0W8F1n6n4Bpjj8exu+fQk/hu9dwHfJ0Wu+9Ifgu3D8E7Byxn4PgGgHT1rDo8byRVwh8F2u9pW9zq+DlUdvPPVZSTNG4VyUMsIEY8Ak/J0ZCudTnnPeFUnvxXFUJFRqR61KcK+BK8S1baYajw0NaE3C3VbuywIWGbBtSKFVYI8fxhLpCU/dRQzg6H7JgeugFqO68TOBI4zWMwVL2elDLLVVr3/M/P5PFVe+pvusxfHpTeC74KqJ2tBmOvcn5mV3mv7v147AVykcFWCkYJEgqMEeARV+VIrqpdAX2Ufm8RnWvcTJri9q/Ktshze1tKy5HABq8T5l//+JTxRJnQgCgBy9flSKlbC02C1b5XHv+7wP4mC5Tb/+JUFe4XRvNi3bYm3/UrhSh9g94AqB62MW3xM4WoKQBICy67uBFaqhdrXAVcCIcOJbFIlrd4wljrMj/c8NF/r2NMYt3TXFFYjJSHCukLgqFomAM0+B2DcXo16kY0HfiTdSuqHioZbAUKHccVBo0/VftbBS80ctXgRQ9DebAp1olLmDnxdkRnv3lTxmAVHdsNH9rl5VnEdkM1cI3DG5GE2ycuubXzIF7u5vPo+dvcXI/0SBjkiYq+B3mKRPm2UP4L25fYEvocGpxGEFOio1f/RjEV3eQ62zq6X7/E0mx5Jb6GqWNrBDa3WBKhVfWeUrrQUw1HMRjyXMnCTXejksx0s4xUHLA/DT+kCZIrBIIbug37/RWCI94Vh6swQBLXZrsduJy8/u+7Nu7hsK7KFwVIEO694t/bsuwCz7QPs5ShwG1xcVzfMA+CMmc+AWc9GjQ0E1W/Oz/vKFHDYDYD99Knz+MfnhCaqdJKqFIo7+umlwfdRGK1qCUNNg/OHU6wnXYyZBcIFBBJxJCqTbOUfViylfEFMoHTzsYIfhDgfWC7v0bMXzjqU304vNZu0TWCelJJx3/nWK5g4/jAKiDhvpNB5v+FPETcMVUnAv45zz1Cv2D86qLlmxS4kj0trBkP4Ck/spmdQNgMJRwEbhqG5xq2/DBfAqYK8s3fQxBnRKTYeu+g32kns0m15xr1jO9f7AvOp+X/OlTNet7Pn3sY6tXNtt5vbvUH/r0739BlpWFTN/Y+ZuYU+/VoZfAADVhaKu2fJ/jrSoHN+FrhflFlz6CVCvxW6gFrRf2PGlsiPvh4qKzO0v9hz5lnPXg73Bf0cfLW0clF/YSy82h5OK3RJtXZsShxXo1Kx7QTxm8epub+PSWaseHumj5j2AzYG1BrzELLyDa/g69gb90kqT7Cd8ExP+zYxZVDiGpKqPRQ+3u0KArX/XBSbp0+D64m8/5SWwcvQ7RYgmURY06X+eVgpEvZluwuSZejiSoORk+BSRpFkMz5MJ15nq0qJVqelgz9kqP7jPj8WmFQ/hQGggOD9OSrDIJT58oostEYWoqUTdgxT/rrzJ6ntjw+zgd30N5/fvusCPa9GwH/+YxwS/yS7fom1p8aMSgE7TGlikCEllvfu+EcLlwIc4x2SqvMtM/QByH9ourmj4Vsm7J1nHzin+ipJiHjdr1n+e3fcnObb719Qa9/7Kgt/Opq4RvqCn3+thnQ9DXoZLBvD2eVjKGsofcbZ9wY/K4aodNpP0afP6O5gDj/IXV3H+27G/BDUK3Ylf23GFAkfk6HUsuBMqPu6yhskSnenmpjULt5Z1vM92v2bAzQL0jmU3ixnXkW5tMie40BivF5rMA84ARL1ZTlp6Aicf3/AeBToA8BdXgff6Gxi0+AHIVP2Us0+Jw35U+lAHALzNJH1aVuWBWxYIKM3cfUAFAGBMKOwKARBg1O98RRHskGoGARhr/ovtAlpW4hzO8dk65pNHehtvECdEBdylZsvjpbV/DeFyAMCF8FjMn70ox9X+lUsApHG+78ScNJw9CQB+XIu94XQst3CJt21pUaMT7+hhNeJtAMBBp6dpqQIdIUgACwCqyeZpvhj/Z7JvaNC99QYcAf36nzuL1xYbThVV2CGRUs2jhRdRabSJk1ygQoykXSFn2w1Ofq1m40XgrY63boq8HplzHFkPcexIMJkTXFAQAc80eW/fnFYvpn8cLv4ooFeFzhAwCN6jxYuOpTf7DrpgU5uK74YbMo2XbX8Eej2j+SZ3zn04+JkWrV7UAxgKasyL7gBCaBFAlw13eFYAUXNkR+YXlVuDX1bicNEil9PzFadsM+YCpwz4zCBt/Cm746eGBc/QGjM78JSWf05z/lZn84MjLawSYIUf12L/dP1mUsKD72CxWTP40IC1xzv8pKrx9eL+00VSewlOuIKr5OgtGnBYcQPD3B7cpegPLAQWW/d0QVUCx3Zd8E0BFQKG/9wx2VjjrUuhdMfPd4uMtaslAA2u3Lertqz6D9QV7wsfr4IyvFhTJMMrpTWJCfnKuGPApP857eT9iG/ujvWKTDHfKpqUw183beAfAt9F4Si1zm4qvruo8bip+G4cCMl63GdXf4PR3gPG71m1BIbTkNcAIQEGJQ7TGjNOLJC3mAFASTPVd0NfEc7Pip5TNJyxJY4fe9qXMV3fRaULp8Ijmu4d3wMW0++znlVLwFc71Lc7dz1YVdfE1AwPUR2qmvJvJd0cMVlbt3pxZ+QJVfc7vtJac/A+c/B/fJeuK5GeACSlTf9gT712JrDdhp8DgFCDOX7ZBf0+1Dqw3kHfBAx31MedzeVVwatS7N2DD9xg5TDrwcrD/4ib6RYF2/s0dgRGjhyZ7+Q6LzItOFdGgvP72jXdjOfTcceAiYDTS95/fHNavUhr4Jv4khpnAYZ+HT6ukHN7K6SvYrnEXPTD2fvv7w+cK25TIj1hDt6qRatDfhP8LiX+rp3zpmfxRWUd74sb8AvncP7b8Y7CsOZRAEKbzoHrRg0PY1ThKi12c6vvAlsese8IegusRv36zdbWrbEv5QIS/M0ke/ZMYDsAzcb7S+2f9rVf7ce1sdvN53DiY2bOY+zpx0CXhacJJa6idfwTQKvAXnHukwFPcoYHgTGluDCyWpRm4/ayfYdY5yMwVIwcXwygWTkYPzX7H2IQnCsjwWQYeCrEHQaOHwET+6aX/LYvCXyjSd6+Yfp18KoU3Msa/G0oqJEd9ZqGfuQMrPai3ot6Pyplqn7Timcd8pto/1uVX/ilF3eyZ58uO/p+74U3+Lrr2EX3CG1a7A3isjP9bQvQFvSiHrBFajWLpTMoHC3adFKBjvAcpDPlYGyYM2aqq9UIIEftCyCEy0X7AnucZ75p/uBR/4Urx24mP7Gnqv6XrO65kacSpJGLT9I1WCPz4XYsVRnwBIfrFNzLJtud4dIf0ccBABjwZKn9097V/xapcGI69N1IAK3lnpxg3YhUIEFwThDXqvEjYCLgdJHfH9mc9q5I2kd8JxP+elXiRdmLegGLoja3KdCBJqmvZSP4U1gxG+3zxRcqGu7qa3lcv/52ymc3H/sV+owAqMZXKz98ge9mHFg7kikNG1P9KLvoXlPbE7IaLwQEespkVR77kg2+5tvS+95zBr2FKf2G/fSVPtQB5SP1pceusCTa1xWCi6f4d5U4rNR0eFYtKet8n/X9TnPJ085d14ZjaJGodaK0aKXW2R1tl4l/UwOeFKuXoEkqbzY7cB3V+Ml46dCpLlBIguCcYGwQTJKwppE8ti9Rb1xSDn8xnG3LqBzqO0etOMR7gHI/jILzAi3/nFLTwbaPzPnpa3kcgLVVrCOxTXxS6Lk4pPkvZ/dlShz2ol4AABiwjZ37s1neh8yBW+A437DsR74ltXQz66vYpMUZBx6a4lvOOYz4psx+Z8BWBoCCBbAIvJHR/ayX+1cBq8MOjrcMlACjH5Xs8gbmyCP8uXNMvm+5dq0EbOCB873YK6a/hW96KFgo9HH+O7XYRmE3t+T7OITwFLKWGt+CYsw/lqCRqS4SPGPp0GSR4PQSR8Ak/E0LxL5ZyzTlOU8pm0bpBlTh6UOuENQ0XOGBW9PbPzKv+aHj6J0AByxOdJCDVj8qDXXbJD1Bv70SQLjDGRgq1QCXKe2/4tqexsrjPizAwUcKcP5KadOzFr7W5N4q63Erujvl6A1tlCl7/syqH6XUn8DhwLExn+3hah4ULNq6NqHFEJApi2Y7cCAkR68fRsCi3btbXne2p/+3igv+4m/WY/g3bxJ+bF7zA+bw96hD73D0veGqWEo3Sk+nZfElQm4xtiwliYDTD1Fv1jJ9U4wmtG9SK+GIaw6qJcNRlw3AQHA+c/h7nlVLJLuGJLjXjofH338pB0B7lOn+nhy9YqHmIqmTsX/Pf8QIwIv/AID9C5J6S/lIf/Mtmk0PuNs/78Wdcrxk1Pw5YFf0GLdUcff1tP9KKzwfzl8Tu5TD/cld0C+sMt3rPzwr9BGtxIeBgDLUXoR1MmVvB1VpF9p0ACShkOnUHTK/lwfDzf+R4AopLjxqLbmGeWsL75wdLl423oKPhEIlzhgwiYCnQr7al6g3MZMQcJz1c8TJKsNlI0eWOlgn5dpuQYMHLXFSiqJhdJugCnpqLyp9/RgAGeOBDD2ffTukv0BMsCJE0OI+h/wm2r+vQtZ85ovfmuXbelZ5K1qCoGvCW/AeA57k5v8I7CBTujlwvtJacY2w40sAQHUphR0KHLEs/SEjbPbULPVJ6gS/kZJbBL8Rexjpuo9UOFD6+rGhoMa/0Sjs0vthFBasCZfcGq6Kla6SWCJkJDj7mSACJvadCnlpX6LeCZlq+FtqH1v2gW+crbK/4y5fqTx0yENfBLkaLXUTtoS174QdmtqLZFUeqGXWI18ZxL9WNMzva7l8wn0LDQceMvj7ODx3JgBI7nKXrzQ0/8mBtQJfHrXNWtNnd5qD32exFVwxPg3NLrr2zNB2CDVewAuYDrv7AwudZ28tWnEALSuppueEFi3gruB3yi1nHMF1fhgr9/0xsEJh5h6HK4Tk1o1E6sPAhJwgphc6NgImAp4ceale5Lh9Z2ZtmcnZN84qOhgVAVPcW/oVr0q9/Y65V/gGL5S/2RNnPms8dLiXb6pS9Hf2tf2TaekzAwfn26q/i+6prjaf95hWXIcDoaGgZjiHHAAU6NBVv2Fbsqm/+RYa23hcH3/nJT1RhUoOMXO+H2DLXMFV4SxrQIE9viX/hEOucMb15acnjIBBcqHzlGgBk9WQ0kBe2jenZ/emd2pvAqZYxijM2KqHLl4OC1dxrdBh9LGb8Kbg161O8mD2lf/u4+u4tltna5/sOfhXG35G7Dshcmw3qzabg/8TbKAF/WoFOihYxDlgQ9UaQAJgXPsCxkMPRP3k7K2/QabqV6ATAOgy0GU+utFQ8TDVeEzB70B1cfik6S5FSSYE5xyjImAS/k6CvPws5qh6Z3g51WTsm0L4i+EImJVruScc9M1y/oB//hdKL3i1vGMne+q15BtW1PjXoR1XJ789AYAULUE0AB06/E9oODJRVh9iu1+rqv+nnsP/m+Rx5HjJqPsT+7ktaNEAknAZLBExt+78PnEMOFrAaYmAQYLgHCESBJMIePLkZW3nXAx8xXh3Ju3rlxknbd+JUUscuB58l3/p5zXnbe1vXsh6f1fS9EJV8ZeS2du08npi30kQRAMAoE6hPSK9coBCX4n0RPBsqWbTA0na17TyemCPH5VQBk3urYB9xL4qHioe5/dF7JvMAWf4hjIl8u/SlxHINKRJkpefvxxSbwavTUl2O49n33HD32gMFXCVzT78bWmfTw4LXcKy/D1BoXXCk+rX324OV+cgTBLWsVPyxt+qqn8BIKV6Ye79F8thNWr+5O1e4uheP5JNPXqx4WmdAUwWKMwtiIAnQ/7ZN/vVmyXRwBQHfRPZd8xSPGcC96PbRsEilH8e/Z7BwLzEB6/EN3qJfdNBSdOnbPNrhnW3+ZxfSH4vL/4Dqz7l9+32o3K4JBaBkIgRAZMB4CTJM/tmm3qzRLRxSd6+qQ39xiVcDAsCwNhuDgWKAquU2NcJ1I63Ry9+l2TzCInpb75Fjpf8xR/DVjbx1lFUvf+9nnOeVXy2HbCBt8FVA3U45Som/E0yAyubJyORspSTJjIZaSQJiwg4GYh900I2WzYuKQW+k7FvzIKykUV8eY+4JINauk+m6oc6EOhRmgMvodwCGymsMQMkuuOJw5qjOFpusN5Pa8wAep1fV+KwY8GdMFkTCzhuEpYIScXKS0QBky7oFMgn+86kenNOt9Gk2uecsn3jLgIfc0wYXcFVCmcnDTMukSr0T/tYP2ydzJwt7OkUsqMJqZOKfQF0lkMKupoNnFHKZnsrmT8GyhQOfvyVhgmFTVjAJPxNTD6pFzNi35yWboT02zeBeiMr8ETC33jIPD5KZzEc/EkAamLfrMNmlGN7b+MNle7fBxi1ef99WKAfG/7mDaQXetKIvdAkAp4YYt/kyQ/vYlLJVomqPYvElLuKy+jOZwoWAHJYFOiUYAgMgp9SQ0fKnXgw1eYRppdLj+FtAbQqdMX7+tdeYtdtMbT8CeiDKunik+OQzcPAhCkSHgMmEfB45JN9p0+9eeNdTDbPOQX7umn93Aete7cAUPA7fIZrYgs1DNuX0fyMv5gR2nQU+iiZ3bHxi/3NK7BEE1X1kJA97DWteHaoXe1DLQC1dJ+ZegzVxTBZgTgTkMYmYZFh4EIjHAET+44Hse+E5I16pzK/aAL7xhS6coWKz7LgT8lh0S1u9X3aGF4GWMTFg+9CgxQtOkjAtd0GCIzm/sAFyv7mmwDg0KSbSZgWmPKNrG2XEodDRUX9WORDHegaHxpRXQwVD4Ttm6+QXuhJE1LJSBf0uOSNfYl6EzPFqb2prTMIwDXI11dhtsx44E+9R74OeOCK2pn3ULAImDW76Ees4z+V+Ju3YamnaInTfGNV8WDP4KtTaSph6jA1G9iu4aH3uRxOGSy2fzWtKAPeMb91jwJvga6BmoZaMlJ/g0AYB0l19QYSAY+F2DcxuW7f9CyikDjrql8ndjjDFQp3Mh+3mlY+ZN5/ixatJSs/c1Ws8jfrBRjDRRvoMvAeAArs0S1uZct+WdX/nZ4P/xeAATdzeC4tDSakg06skeKAUrP+96W2T/j22Tb6Top/V4BxlH2B6AJYmEIXNLJ4ZSSQXugpQCLgOBD7JiCn1Zsu74aPlti+H1eA6zNJf+wKrvK5GsF7KByVenwGbOvH+Y73bwKgwA4AgEWAEbwtcpCzNTdWnf620FEh/kjsm11c4cagxrRsi+xtH+80DaIGfJeARaDLwvadFH6ZMbGDU2Ima1KSXuhJU0TC3xjyw74k8BVJr3FHHXnChRYYv4Lb03/VQgpWxa4dcvQ6lt4cUDT7mupCkDKf3R74qHQA8xXoLFrhkh312Z3rAfhhBGB4bVtweakFL0xT4wlT4s0VAPgrv12q+gTOSR7DH6pM+3KEhJyDRMCjIPZNQGL7xqgujffyKTF9xh11luQWWvAZrsGuYwZsc87f7HOF4JacPbhFAi609NxyvC6r8ijdHazz27Ccj7l+HA1CRWutT1Lok6n6eUUVlrL6ip/KWvt78fsZeFOElLC98YvBpqdV1fuxDxQsAgCkVrdyLAmCYDIZKS8hyxGOQOw7HgkW+xMX5hurvcjzSa7clyoxx5++E8U5dfKLDKoloGs4XG848UPDhQ+aTt2BC3UhFGkPPms75yqW+j0Qohpt8pN/x6EqLPcyld8skZ6gNWbWuUPSJlQN3e4tWUrsm7X4rOuLzE61dJ9e+kpV3S/EIfzwXLJ487xj7s8muVplVpIfF8+Zh0TAYfLgAzTzgW8KixOMs2XM/f7MGHQqJLhoxk78BQAwJbd7+ToO1+HU+Qhw+NBRVfdLruOr/g/XotpuOfn1IoljYN4mnATT9V3n6ctL1p2ET4J2WPBb5szGgY4bpvPdEKZG+3yW7jas/amt+EqD8yXABlcZ1HTYwaABFZRu9OvGm4k0tiM6vSPBhCxHwtQ2ZroNmYfYdzwmtK8/VCkIhsTpnfnBhPFKnLm/Zr3i+HZ/o0m/a7tM1c8OPIXLujGgK1IfG9pxNQD1ph+7dv4gslM57rY1NAKosv4yUFJ6dt+fpuF9EKaBde+jr5I69I6ARYCY0N4FAMvmxiRCI15ByiQXZsjmchwgudCTggiY2Hdckol9/aFK4eNLxSmPlPqT6G3yycSp2RdRda86Q2L+s176ijn4P6YV1wlleoQC1tatcY+jxX0OXJfyGgCEjFJV39Rz+D8VOOKHUcA1BtwCQEC5Y8FdI7OBM+FgIuAsp9DHgIl904DJCjcNr0oQDNEe8ocqxUcGmzZ1JvMWoldcqC6GoULAInP544xmEw6Eij8+ad27paq+Ke6uDjwEbcUU2kvIAD2fPGla8axauk+L3Uz1htB6mX/TLAo2dA/G3X7saEWuf02QF9fSmaegx4Bz/ROTqWUVYkdqS+1QqeCmodQBEKJ+qeKdvnhxycWAOMnLYtzR3xHUErjKDNb77bgCgA5vaNa94JUsxeHmsdsy2k2sI+evxQUHX22WHMc5aigEas67wjsrmdJvhOqKmNDtrPv5uCPB4mcmOhSOHg8mI8EFQuEKmNg3AanO9zXoHqH9Paz5eQCAPlyAHhCgQ6k9FzWcfEQygX1VPNw01DTnulfsiw6aFc6izYbPfhB3c9axM9WmErIB7f4/OPAQAOEwAHg2PTC0U1e00YZjURuNycaK/vBQFEcmBxcaBSpgYt8ETKLaBuffjE/LoA6B6wNdBugB4PgnWDY35oqTFg0nb8dUT5SGnsCY+s8R1DRcEHijVDlYvPh17jNS3CqvKJUfc/iHf6jmnK2DWvzG0XYTqgEAXlV4JFj8eER/KYZLlgrzRzk4bhBMZgPnGQUq4Jwms/aN7n8e0VWpHcsBrwRqveL4dt/xywAbFpwHpRVATJo0JqXhyalxvL3C17gp63aC8Bdx5oPyTtMgPx/lFtiMuPQTvH2e+DyNbTyun2J7CJnijP8vkX/PPrtZquA965agDQDgpqHiRxyM0VkCANw0dXyv8vhhx5VXjM3PmgozWZASpCZl6hRiElZOh7+ZT7kaw8glQ+mGivcZrmGq7wZdM+GOyWRpJd5GTPuKSf5KhrRkh8U5acyFNRoXDwAod2CttqXF5LhLju0azUto2G/Azcw5G4h98wADbqYaXw0FZHbneueua4HRdTm8qth+EYR7SrTY7cB14ucn8snM/mnxhClScALOXfsOKbTTbd8plXqO6lXr7f43AOEFYfp1mChSjFh27GPcpkak+8G5kVOkquGpMIF9R60/GArbl/cANgB+VA4FNUbNn+SDFnDVXMO/sp+9Fns0Qg7Czftx+Y4dWC6Ro1f8WwPiByAENz1Kw5GHm4YrxOF604qf4OOKmfwMEzJOYQk4p+073adIz0ILbnpYNl3i3KSR46fvyjJqwV0Xj48rIvKbAQ1PfIroKMcVAtcHvitiXwBy9IbWywKLFFzb03DRaFk5ne0lzCAnDXZc1dd+ddF6BwULeA9cPFw8uGM4/jG6B8HKR0YlRCW7QuD6KFhkR73D3SSJyLkFUQgJKCABE/tOnYn7xFwhin9XwDVatOL4JzEvpsWOo+zrVTHn3g6uD2Y9+nXRGp7iWSY+ewxjc6/cNLoHTfO/xSy+R7NxtwJ7KFgoWCqr/+DAT+R/s5rb7wOAkyTiySv8uNaPa62tN8hhAWzgPeA9FCxy9DLGW7H0+Kho2BUCd4xqPFap+SMAqONUkM4tcvcymxFIEla2MzP2TcNttWggtUTgjFp834HrYKgA/OGXxsyARLx6QBM0cmytRzfNdj9F4V3h+CIcB5apgHBn+KTPktTZYxg79CtGNrzHpb9U3n6mjD3kRZ34SqCnbLbs2sCQEqgF9gCXpaV5hOxBju3GxQ94j9RFPdOr1HSwi+4xeZ62clcLrkugpuHitfxvBBi8O+604ynfgmsj8/cIBUKhRMA5el+WE/YdMZMY/NE1XtSDLhu1Mnm87KRkUqjibyPO3PCqhqveg2o8Brps5NUUz5KAifeNGfp102DlYr+iZuN23aHXHHgIqkCJ9IRauk+BTpmq37uhvhe/A0Dsm5eU4X1zzTcp9CnQiSapruYNap2dXfwzk+dpc9lm/YpXw13TvMeB9RQ4VFh8dGM4ZwIAycMqGAoiAib2TUDy9o25FozKkBqeyyiGfZXVf2Atl4Rfip4BOc6aMEip01g8FyuHi2dKvsnzJnqx2b5jvQAPMCbFdKKzjI2PJ999Lf4GjlsZzT29zq9LEPJILnL234Y1Nl7+FNd9W0nTP+hgd5HNJQn6Z9NfPsO/MskTEbIbGx7Dzn+lq1n2/PvQrNJVvM713YtDVeYlPabQd4fa1Qrs8fF1AChYqHV2k+9b5g8ehZuGMv2NmeGZSISUyP/FGHLRvjM56DtFAQuCAf06HCiDmgZ3zCR92rVpNZqDProRahpqSfi+PjIDEkig4QmIxJpmPVN8Y+CjUvOFP9MefM6LeoG+BNXFseea9Ikm0SSEBSw//neqqc/XvNok+/FgoMZ+4Z3wUlDxOPiZEX+WyVzmVfdjrxVYM+1tI2SO0qZnhZBB8BtjM+yoLua8O23nbCjf+5rduV6naWVlv1XYXvbhMhgqoJZQx/cKV1ZSFBeZJT+2HEc2L8kgQmYDJwkRcNaRE/ZFjIDNesXx7T66EQD4Li1aHVgPumZEihjt4ASMZ83oLCc3je5BLf+bkhUnze23AuXixSudsk+GuPOOXCGcb1No/t7fvFCL3f1NCwcdn8N+GhoFbMO/w9Uf463zp7FhhCxAju1+XDv2eSWeKMVHjsYrxEUqBz/HSFoDAioo9HG4HoaFWPYpEXCBkOdd0MS+CUjbfIZSO0zwqZrCiZ3Hy0ukJ0qWn8SBkNn1OECHvRhJD44xsVcFwKB7hLPfE65ZHzmsSNzqFrzHMf9Oqv2HAMKjv65Q+FziiaIr/41t8BRJUHADQE+F7tRrvoovl7ifsTdfAQjA+ZFJoQCIfQuBuPYF4MWdXsBk8/QFv2So/rN8t/WM/kmF9WUHrgNdBsaf3mJYhGwmn5OwiH1ngPDFotQulsGCioehwhy8TebxmYO3wcWPmnQBhCcHxxQi6AwJbTqw8lEzaMVpRTGqEzdwhUwrfsKU3FgkdY7MtgTC54reMi6RI489fjKM3SXmXD2D3u4lzKxvnpH8UtH0kRwf67Al5bMQ8hpz76/9utV+4+wzNU9Rn9+vQCcMFaiVwNA14b5kKnDekOcRcA4xw/ZN6TucbCqm0g2vCmoJuHLvkToKFoEvB+hwrrJLDgAImU5+1zzviZG9XCHwpxxYH/4xOn6NJnp+LULmDx5ljLfKVP1K5+HQxk75LgvHXQ+UAxVAZDJlvJg7Wpbj1ccfj6Rt7cB13iP1WOPV7W3B3IDr1OokdyTkPQbczOF6U9cdCEmG2jSYD+wIcA3/gpZjYOaK2yTof84JSFHoJMnbCDi3wt98sG9EYK4Qo7nHj0phzQVavAiuDy4eXJ+WewJcH7i+geB8dA+G6/O5QnDxWrRRsAAYFb/GPKJRSwD0dv+bfU2DY/6dpa8fE1DOVD8aPt1xq2bh1thoODrgZuWGDx6KKdQ1QUycIFYedWcgNo8GXSbASO39yOusY0/9lF7fXVXfxBg2JPWbJOQ1EoQYzS8GAguswS87sBYqXqAvwcEFMCyEoYv0PxcU+RkBE/smIC39V9FzkCiKGzV1hzvG0k+BBvZ2law4WYKfWNu/VLn4j3xlFdN7D3/EVCR1OvguBdcxnLTl8aJemL9Ge+IJB+6Iil8RPTMyTKR+kJoWuEVCs43CXhdWKdYd7S25oRIveLvr5bCUvn7MSfFQF4e3FxflFXdECFwfrTGjexDVxeHF0mOYRL90NC4evCfyk0n6ELySnlnfVR4+CJCazwXFIWBJ9M/6K+6Qefr4dlM4SxHAwc9my7aeYX6RTOczIc/I2wg4J5iB9RViSNW+qdUBEEeCl81FdTGqi0HXmNtvdbWvVOIwe+Q7jrbL2IHnaY0ZyyUGbJOjF3wX+C5Gc48Sh4sW7fejMjxm7ArBFTLp7woPHkc/oqHLgHIBRgBoCwIYPK9GL31Fp2mFJATeE46zgeiyf1h63CT9cWCRQr/m0fBxEowWT8i4+9oM2KbEYQp9AGRn3Thxrhd3Tv5EhBxEh+3AHvHfmk0PGnCzX24wf/Ao1/B1LVqhplFdjAXnn7kwbF8x/J3iatmEHCIPI+BcCX9zMeUqKZTu8OBrdTFcC31Y6HPxAAS+HCc+sWO97/g1wCeajX8zvf60K7gKgGPN9QrJ3+Xo9fEeuMRVg0Kuz1+KE6FR5bREwiPKo4JjX1OdwF5sZVci+AkqzmdU/27q+bHs3H7206dGQmquz7TiGfMHvx4KanzttT5cBkO8xOkkiVFv9MJHfNesVb/p032p8sMXAj1l5vLHJaeOMOXfYFM4OiEfsOMRLL3NUH5bUb/T/M4tgJU5vCXAK717vuygbwpPnFO6xeEb0vlcgJAIODNkxL7TG/5GI5pMxUMtgVoCNS0OiwLlfhgZ4ZtYbXB6vtJ/1UJd9Rvs8rtR4lDvfMuPSsAGvkuBPSbpT3zNtdEB8cgDiFndD4B651uMsBmMHygvWvQO2/v0QHBe75GvjyxH4+IBm1m12WS7y4daH+pGSleOHSqekPHsC4D3KNARKFEJO84NVKmwXKK55Okq7eOsbdckf5mEXEZ58K/0sdND+1WKvpcBO9u9xYv14dhXtO/0k5GrTa4EQpkl3yLgnPir57N9S+3hAVQxI1ocfFVLIkWbAQTcpUzHFgBs/U9Lz3zCuB/1rFoqU/XrmFYcAQA5eofW6tAGiAGxuERMzEptvAewialbcoS77BTMTp+hXm9/+ezl5coDR+CUOGDE8H4ULAJMWC7xDTYpDjb7+HK4ANBQS+CmR403T65T2sWD71Kgw9dwoaLlCGiV+dNfwxRA+3H3xuXYtXMyxyTkON6ar6pM73CWe8F3AeWgy0YqxIn2HR3+kv7ngiKvBEzsOx6ZnDgYcTBocF2Vmj8GFim87XUU+nBAiUuk7AdbseukfJ2V27vFJN0CQFblYfueU+B/fagDD7gAALxntuzBM4HbgXIAon1pnC3FUbqa7b3wBsFvMO16kg6c7t+0cHbL44GLFZTCrmjr8A0vQwRA2/KGrNrNqDeH6iTyjl4Hf90oB0canBLR4e/881SVz2mtf+uR/wq8ffbQw2f6tgMI7tozpd8hIXfpMpjPdGmueMG5ay2AUfVZCQUP6YKeUXLFvonD31HLMCRgbPcaXcM6HzG33+pFvQ+1Bv5hmcen5P8XSxZwbbepr/gjlktkF/Szc38GY5ducasWrVErqh51bLxcgQ7AJtpXDot33ldlqzwBRl3Z8QI8RmGt3o5HXTt/MCCcc7b9m+I5xVV4w9OcAL6bgSsg6Q5S6+yKpr3gu0Y6ukXiJnyNx+jNtCee6NN9WWLj9V94DOWzzgS2D79CVj0qYBbLSl8/pkCHAntiO3JmoFw5IYvJHwFnefg78wnPImm378QkuKYMjwSLPxVJnezA817US2d3ArWynf3m/p+zup/iTAVz7BH7OQ0AKFgAG4WjShzub7lKgU4tWg3YJodFjl7TqTuLXB5r+T/DKcX+BdbWp8Qj2/EoEHS2fUGBTi12a7FbDosclvBJF91rcX5dbjnja75Ai9aRceLuQXQPMvy/j+ROJ9Bw9KvD8nbgusrDL3Bd17tblyttLwGHpvSbJOQHrFwiCYaaitSyt2NfmuKEN0KOkz8CzmYypd4M2DdpFOg0r78dJzhmzmPBXQ0ASuSfwk0xp79rPHEv63qBClgcDQ166SsKdIjuZEpu92+a5W1c6m26EE3SEukJmaofgOA3sIt/FnP8kqYTocYiupqlZHZaY1agU4HOkhUnZVUeQ+DP/jW1ALBkngDDcJDdZRLuBsAvnBOOiUXGajihmNnTW0rxcbn0NQDFTYfT9esi5DBW45nAdn/I6LuqNqX9crQMFiF58kTA2Rz+5krgi+Tsm2z/c1zC/W+2sFCPPqJoestx+orSpucAnPH/BZ8Z2K7XLHi+3P7L/l0X4IMFAOTolaOXQh9UAefpb5bteK+s+YPSgaNDa3WBRQq+sgoDOrCxkYQ82CuX9PIL5zg3foFdvqVohYtebB5oP4dddC99+LR875GASqk89GrRJrsCHRSOmqRP91+1kCm53U/rw2U0ohLHxp2LHLMZQMEi3hloF//foOOS2I0JBUrHrMPPu3YOVySN/hT16zCVVaizmGy+LGcJeZWElYXkkH0nf7pkrh0RS/EeChb1iv1m7nHNeVtLBz4qWuU82/znmM1teEy2aSfcNtkpj9LdwS6/mznwGNQyaJ2AxILfohWYx9En/49HHWglVp9G16gjSHYKPqwuxikfVgO15gvPKM5p9s2uM7b8gcVrwGtn9wG4B26pFtvk6JVVeZyfbHaew1O7DgCAi4eahiveROS47ytc/cpWqflvlnrecOGDflpfvut3tkQ7EwoC2aZdRtsjfDsDSACMfKgis8/7dWQwuDDJBwFn531Wzqk3beFvzLCWuETuSHXGcPirPfG4g70ZVcU4Gd/fgSOX4LzjgSqVzOMDwDp3mDxfQSBox8PhLU4awgktPNA2al/pxjccu64EHrIBs2WnzwRg/PCHlg9fADotRfdh6LfiZrNlPxLkFUVSOwC4ZaZFd5lVm2n0COLkJXEGVESxMSaODnyHM2soWNj6R7H3jLe0XjHYacWTE/+6CPlOYOdG87r3FHgXEKfP0eGlM0U6y7T8bxxXXpHBFhIyRT4IOAvJS/umTGQ2LdfHVN8NwN59JQDf/Gt97bdjzdFZgXvP7vvTuLt3GQxdN1sbrxFUBpw4F4A58BK0jyOJxcWDu66M/FtMRbbgBQBVS38gsfvZrvBLQwGVr7TWF1zJVN8bfmpA5224mDnwKOv87sgMKHXURKPx4D2ajdsByAIHXI2rSppPWPEko93EOsj034KnhlOU/kWBTgBq6T4zdxvomuia546GBvRrhdJki2FRvHWaWkqYYXJ+DDgLw9+Zt+/k8q0iJGnfuOFvbP9z3KxOQwXb/RjUMh8u0M15g+m7UdH0tEn48dneX0Yq5calHxdIdgyV0p04YcAV7wCAYwoj0EDPwb+yXeHlEJR4XCbz+Fo2Qmf01F3Ezv2ZWbUZB5TlLTtCNbJw9jX/rpb/TXhBp/EYfknYpXe23VB64NOhXfU2/AwAsS8BALoMiv5OrJP6sALLJcycR4cnv/Hg+sB3mVqfgpks8VuIkAg4neRc4Ivps29MMSm6jD3yCIWj9tNXAlA0d8g0vlm1/yExhsz7xz1psEmu4tv7mn8NAG9+Ppl2Jo8Xd3kDQOAl8HDuAnA5sGbWqq+d9T6GDx0GPEUvNrMDzztOnA/YgLKwaNVR6TMRK/MeAH4YtfxvcK6EuvRDYddJBOelt8GE3MVbWtfffAsz7zTb/hrQwVTfw3Zvqar/hcQxBAm8py8kpTkKEwlT25jpNkyJ7ImAMxL4TvEIU7EvYgQ8nn3d9EgB5+H6kUocVmo6WOd/AED5IthmaO5TUszjqAv2VX74gm3JpvIPd3pPL3FgfbiI4HhEvS+H/Jua9b9T/t+RnsFXZ7DRhJyB0W5ClcR+ZL0CnRx9L/guLDgPJitK7RTFRZeiHG8a0iS6oIt8SYzcTA8yd3+mTp395HYXdMHad4p9ziJJZl1N1b7xUC7uYKnfGta9TDU6im1vJtXcmaL85GOawd2BKpU8cJat+VloY7g7WizIFWeHqCcHm2rAlDjNN3rX1cXZkkAALI5/CUklOk2rt+lCxnhreFVgshpSQUK6oKdKli/oG5cpBr6Ymn0B2I+sN0nvkr3XLxStFBqOV3FX97iew0kDVh7H/gXJtG36sOHnaAUa9mEIRepjkh1DShz2ol4AgPL4Dg6Hvx86um8tumD/0I6rnQdnttGE3MGPr/QY38Uns5gjt8ItUzTsQnPQZyLFSguRHBZwxsPfXFQv0jHdKM7E37jL84mM6X+GuN5RUCNDv+ncb1nl1/Swz5rO/Zb5JDJu3wimtic9gYs95V934GpG06hUHQLQ2/1vAoyRUprDhOf6ShDCoaoK1f8jy9kQEiE/Dd6H5V7W/Rxjv83XvAYL9Cg9Nq3nHFJoM9gLTRiP3O6CziAzad+0dDiLTGjfBH3O4caMTbzyqsDK4abBysP/EAmv4MtHJgFr0arFbiUOKxd30Bozu/xuc/stwpsXYaHLvH/bVN5X2jEHXvLgO7AZgcP8xVW2JZsCVSp5eEWH6OoaI/8OQcosbuwt+aZp5fUz3FpCttOwr2rJ1cBBLGWZytsUe/8GgLHd3Ov8emnT2zCFx3STGQAm5BM5HAFnihlTb9oLWk19jaNx055dvOZzLziH1mFPdfQER7h4qGhYw3f3flT6UOdfU+vA9bojf8SJc3GpY3b7d87s246spKq+yV7zLsdfj4/O1ZW+IUevH0YBxtEOhrg0E4W+s3NvwDsLzNbsupkgZBxDYJv/kNEkfWzg4Dx23u9BD2r3/oY1/A4LJIIznH6V6TYSMkCuZkFnpP+ZqDcWUcBeFdw0jluBPkXTR77mNeGEYd4DwIAn6WqW72Z8qJWjt0R6wrz+dvxjftGqd4p3sGrZvqg1+7KVeRxju4Fd/j1T65MDwfle1ItPCzBGVjkU87qhC8ApZS++B2+uyFxzCdlHwz5tS4sD60HXjCwJLK7XGWXfJCPgyRXiyFQXNMmCTgDpgk6WmbFvGnubI0yvfYehcJTR/MIfqjTgSfBd4LsU2KPFi0VSZ9CsoKtZnaa1RHpCdkG/vMUMrXRox9Ve3JkD9gVKanewzp34W1X/VQsdWKfEYbEvOmLf4a7pEDv/fta+k9iXEAs3x9u4FCgfa9/IJtH2TQApg5VPkC7oiZkx9U7HYRPYd0L1JlplYUzFK8GwmuUWKSR76TksE3rUU3cRAOXpDnb23Rrq/xAKOT1fMcifgk/ibzDpgw9aT6bwLjLLQPONqOaw6Ljz3duM+EEIUj8qh6UbhkIfIMHBOZlqJCGb0R18xl11cexU8lI7CmD2UUBVSoLg8chJAc9Y/3O+qhej7ZvyUmhx5x2pJUCFr3mND7UULPqqV82qzdQsWyl9VN58lsP1wCJas4mtfxRmjbVja2pnzDjdhrLuX0o3/c3C38D84yE4IaDCj0oAcoQDF/7iKgw6MGbNdUKBU7RpR/Fr2zw7lmDceXmxkAysAoF0QcdnSKGdAftOR4ezSGr25WrQr4tfxnksiTejy4ByAUZX+0rmwGNW6XXK5g+4C38IrAHAOncWqY+hw5TUibIMD/7TtfMHODnXc+lSIEShT1yoGACFPlpjlg240anJdDMJWceQoAMgLF0O3gMXD1cI3fKYmXtJ9j8T8gwi4DjkunqTX9pIEAzo16EzBLMeXlVYwwlkHPN8zPRfAGpadDAAtv9JecsZbs1dULkjrw/tuDrZd5KdnDTId57FOlmR1EmhT3yIrwxo5hkc92e2dYQshDnwqD14FVi5AnvAe8D1Kawvi7P18r7/mZCY3OuCntb+55lR7zQdefL1rWol+OAT0DWo1iuOb/ctawovEp6YsfaNQoFOhb+T1pgde7/oXfKlZBqWK3B4Dm91a656ofT1qOIJqoBz14OZaxQhe2GdO4GdWut9JdKT/o0moediRflRX1emm0XIAkgEPMK02lcMebPRviJ0DWO8VT/3QZ/hGmACucZu4I6zQJADa32oBaDVtoGl5HgxmeblCkzJZgAyVX/kAbUMa45mul2E7MVRdOPQWp2+72Xms81c2+ZI8Q2M6X8mA8CFAxEwMM0jvtPqXaTS5xxj31G5V9XFbPdjQ61aYFio4znYq4pv38h69VHVknmnCWVBxcpX/PTKosbmZBqZE7DOnc4Dt3lWLYEqID4CZYoq7/cz3S5CFiORcIN3WSuu8TrrlNid6dYQsoLc64JOO9Oq3mk6skjyY72YcNKRKwTY/KgE1wdXGaqLoeIniIPHL/gMQA6LTtMKBp45F/laNmrW/86544HkW5v9aD7/DEKAWib+OKTQOtvXlGGlB9/ObMMIWQUzbwN76gkEu02B70nag2fW3enAeiybi9KuSQwAk0nAeUaOCTjtA8DTYd/p9q5ISvadGBcPlOsWt+rYVtb5HbgWAnScRcLjLnAUZV+T7EeysgG7c70CnfY1Db7m1ZjtrFp4R8/O/Al/RZw7H5gtu5a/vEosMOQrrfXjK/5Mt4qQXUhP9p98VoE3fLjMTD0GAC0eLFOFq3AAIPnPhU2OCTiNFJR6x4a/sXN/q4vhotkjj2CpCgdPwcVDXTyubrljMCyMe6KBwALHOTeDlftwzezX7qA2cc6dD/DrN6Mj1SbnAFL1gK+0VoFO8Ucd7rXj4cw2iZBdBOf58Usf6kCXQU2D8UMJwJ2g+DMZAC4oClTA6bVv1no3vOOExSaVbkAF0Ib6rUKbztu4VNhRDhcNiOU1onCFwPUx1Y+y3VvCVW3Dz0dyr9YrDjYr0CmvO9u7/IahnY0ArK25VnYjOVj7Tnz0Ai4AAB9f56v4MvqIgAkjaDbeL8iK0SwmRgx/WaLKTxIKnEJMwkqXfac7sTlCSlN7Y/eNZ9/xSl9xh+930Ddpd7RSOAquDy4e3YPDqwoOry0IG7voHi1ah6sK8GH7Dude+WEEIHEOlew8Mbk25xDGE9/38XX+UCW4OehL66AAIfdx7npQaC5XoIPCUc0lz0Dpjik/WSD9zxlfuz1rKdAIeIpkVXbVuAcZJ/Adt/CkigdogOZc91L8u4qmvb7mWgoWgVsUU8PWENjmwGWADbFjxDZxYT4BFTTYYFMxWrrBV0/9vWQtArQ4ca4wWwdWnum2ELIRfdEroc/JZGfcbM9zULkSb0z6nwuNXBJwWm6jphL+Tt27ac6cSnCiJO07TrUNAUZt/x7Fuk7ure+B74oRrdCmq9T8sdf5dQEAoEWrA+sxvCyub83lusPfsy/Z4Htjg+aK3zl3Tv3dZC92PIoTVwHnw8rNpr98hn8l0y0iZAWlTc+FQJU2f3KG+Q4Geer0AVSE5+kVWvhLSEAuCXjqTNq+k1bvjBk3fLqEw73JLrqgpsGV+9oWURutjPFWe/eVfhjlsIh9y1rspjVmi/NftNgtltoQYFCgQ9wGgOntH+EC+OyXz8a3z+z8SxreVZajXYgTnBxHiX0JEYqbTxfB7sBa06y7AJgXPA6llYz+EmIoIAFPzr6TU29WeVckjn0j4W/MfF8XD9jk6A1BBkCBTjl6lZoOr7NOXP+HrXhes2orvc9c5HYOBTX+jUb5LktkUaChoEbm8ijO+ZvkPMHYcaMFz0/x3WU7jkolnqBgIXOQCBEUizt7j3xdwCLzidVg/DBZC2TxQUJKFJCAJ0Gq9p1J7yYjXZH4ge/YzufIpCPeo0UrhT7w0t4Lb8CFqDz1B9s5V5Uf2gm1DCwAyHf1svRTuFCiMT0f2KWmNYeK3E5xb5mq31N3ka95tQ+3pf62chKVbL+syus4nel2ELIGy5HrlDhcIn3FzPwsetZvhLH9z2QAuAApFAGnGv6mRb0xjpz6kE/y0o0wsX3Hlruiyxz8dRSOCm0GNMgwoGOP7ED/8/0VGxTMTt+R1ajv6z9xARRy48EfWNifYJVLpnHJeSt97DTcMqgCct5a2tTen2+1N8al/6rzAeD0a5luCCHzFDX+dWjH1f6GanVwn+wfwwvRk/CXEI+CmIaUkn1TnVkUd46QP1TpD1UKgkE487mIAsUnUzhyqDLmkfy+AATBECflKmapQdG+bjoc/kbqOQMAtGgztT6lKXsJ8zndyV+hZFC39w0tXtSotmu1bbBXWuY9pF/yI/RolN0fcvwd/MI57DlbA1Wqon5n+dt5nXw1GufOB5yur2S6FYSsQC7htPh+kZyTtAbYeVsjU49Gb0PSrwhA4UTASZL+PufOEJbrhNKRm99JRLEpkWisN4aIfaO9G1XPGYC0dsDp+UrpBa/aTzyC/Y+wRaewrg87L3aKL580WE8C2NoDYN33QxIpDKf7i871Sesl9iGggByMt8gllQAA5Yd2BGRKx4E7uGUXRXc+Tz38JYWg84+cEfB0T+VONepN9GqoEpG17iPHFwzT3QE1bm/zhGsLcn1avOjAdaDLRPsasE18RdId0lS/5Gy+pqTphYHmGzA0F23jHKTtYitQVc/2yJ5Cmce08ifm/VN4M7kGo/0l68h0IwgZZ/XHvrf+W1HdAb0/bF+S+UwYn/zvgk7c/5xSNSuxtzmBfeN0FKtpeFWiieP0CSdE3D7uI+42sftHepvdNPXBgfiFnYfD39BGmRYvgu8SY18fammNmdM/yF9c5TTfCAwNNN+QTJt7DjdXDd2OA0pnxerk32kewNY8k+kmEDIMU72BeqvTjn9h6d+Hnxq2b/TNN+l/JkTImQg47SQj3eSzmsddalctgZsGVED42yi+lDgaHmVTrmbsMNLEIh89xUgOi3DcigXjvGW6zLlrrUn6NIJwNDQoWj6UozfgLlVseqWoz1nM7K4I/G/P4QlOGKGno9kkKw42l/ZPvG3+wDhvZjPdBkIGqaprYtWPwlIaXsozXuZzAkgKdGFSoAJObN9Jezd8cNGOkb5fVwiqqGTjKA1PTL8ObhpuPUwj+068S4ThBvhQR+Go4FoNRK2vMCrlqtwa/LK8ycoceoQHY2+8QvnhISpgMVt+PUvyHz2H/zep1g4jm+M1f/ZSSrvkOs5TawGSBV2oLD7b02GDQYtqyYh944W/BEI0hSjguPadonTDR45oNcq+FPeWgNVQS9BZhtph+U2oUtGjXhW6B8F7oFJB6R43nWosEd+7aSAElGuxjXNdAnVUytXwEkbiuK8cFt2hN9iB3xnWPShByFN3kU9aD2fo7Ik/J3vSYdjPCk5Fmnm7PScz3QjCzLPuPQ29y7nrOAwLoZZMIvYlFDJ5LuCxA8DR9k1GuikXmRIdeaAM1bS4eJ8cFuOsm3oOfxt0Ddw0lFGbJcargpuGmPoY6ceOfL0jio3+wkfnW40d9OW7wJWLKytQ/LsCFolPa/GiF/Vy9AZ6yjRXPc0duB8efojvEVAux98nV+CpDL/04D8ntWtuIpl4E0K+0bCfbvnUiWthqAjbNwLJvSIkQZ4LOIaIfWPUm+TUoGRHXr0qqOWK49t9qDNJnzYHv09V26CpwN4u4Lz44sSwblWj1z0YiVZDAA0VHx3aGo7/kFvwo7CYI7hpdA9CLQa+kTDXBkCBDt3iVvbIdwAocdgLCDBSsADQS18B0H/VQueuyzWbnnHufMALAChf9bWz+5L53cRSWPYF2BMFF/QXOvVn0HKSpy+Fmh6x75jwl/Q/ExJQWAIWEe0bJ22qX5fovjUSs35cAcYfp6MputeXswKgGvvMR5/ACYNzJ4D7tfi+o7sG1cXhzdjhu+bhUFV7/AmH4Q6oJXCFwv918Qrs0WlaWe47wEK45ADg4qGm4Rrk6HvDrhWfCRMKR7oAAApHBRgN2ObE5aUbP+J3mRTokKNXubhDyXYE3KUDwfkl0hPmVfcrtHv8oUpmzmPskd8DD4i7O8svA1LugiYQ8hgjvulFve/wSaA86nuXFCQFmhCNhKltzHQbkmJy84BjuqAFWu/Y9aM0tYhAIBQQSjxRtNFWtu+Q11nnRb2ARaDLwuEvEBsBj5OBNZ6Ak8mCnmIhjiJfJieqy9wFNSsiWQouAtZu/OFMRMDHrQrs8TeahKOrcCLcca3F9x30TSMRsBjmThgB8zt0mlbW+R0YFg7vyIejXpHYCBjgjgHxI2D5LosPtWIEDBZjI+DKwy+wkt+jK9zmkqbnB5pvnPC3SiDkPV7ciV1wAkYY5bAIMIIHQIe/rQRC6hRcBCz+Y9rHgFm5gns5Mgas2fSK030N9vZhwXkjo7zJjAGzcnB9AOJkeUTGgGN2GRkDBjA8Bsx3GbDNh9rIGLAWrV7Ui2PAShwukZ7AyBjwHufOB8S9Z6362tl9pAuaQBhD/RkcPgm6Jv4Y8PhzkOIGwSQCLkwKS8CY0SzoYjELWoE92vq2cBZ0dTFMSX+LRCUftwLAAn3sNzz5LGhXCFyfAU9yuB4YNwta1HD/VQudB26Dh1fy/yumRvsxmWUGCi0Lmpm/geRhFRwN++mWLh4Xjro/HlOBMpleaCLgwiTPu6CLfI4YB1O8NeLg6A/9eDIe+20Zq+TIF0wQDOEv3nLAK4EKQIWfM/ac/Q0WSNA9OBKtJjMPWOkGVOH6zGPnF8adbhitZ/Fc0ZORxLt1salYHenEdvDXATY/KpVVh5zv3mZY8qCj5ItFQRsltfnbv4C+CVoal4KyLxBOOScUFi0r+XXvaejtzl1rgYXheQoiicezCAQAeS/guEQ7OEIyMg5vGaXkGBmLJh7RsFcFtUTAajB+AFjuAZKeIBjZrFoPFENpTXZfMQQXTSxq2CUHbA6sDdt3ZLwqEiIDPPww2pdsYA59g29jQo2Sso73UQenZtWs81LuhWbO2VBotTicJ0klrIKk7WIngMW34IgV0AM0oCK1OAhJkv+LMcQlcWeOPGARHxMeRy7pFR+jDi4GxKX28PdQFJ5YzzlqZCjBY+RYpXaoeJis0ftOQPSWww1QoEPAIqglo7JFRv1o00tfkTeb2UX3+FBbtuODQE+ZIDOajN9ylK+rqv+npE49TOC0shL/ntIuuY5m7u5MN4GQOY7Mqqq7Dwsd6B6Emw6PBA2nbaa0BAuhoChQAQOgeOuEYyoRE0/o4xgNj0jUFQr3Ho9W7wRti97G0DVWvXFUHcNoB/thDI8ixzxEeI9m4+6B4HwH1qMl6EOdH5UyVb+v/ctDpZpBdm3P4QcSNziaqromc9HP3E2XJL9LHsBqnst0EwiZpKejmTn9bYp/F8etIw5OmuRL4RLyifwXcOLUA1HDSWY3TGjisdEwXCP2TUa9o9qWRIgcP24WiYTCKl5Ytjw2WVpExYtBsGRXwIHrQNeI85cU6OSdJoP1fvq9Ho3peaCopOmFZNpcVd/UU/QUlns1fW8l/07zAKbr1kw3gZBh2O7XhNW1OvyJ4Ye7f+IFwUnOuSAUAjmTBY3JJkJjoiWBo0lyYeAI4923it8xQTDgb3Ow3INS+4xVpIu/MHBcxJt0Nz1qWSQXL67NIE5PUtQd6dE+Uqp5q7/5FgAoOoXL+tB2cZyjrXtPL/kvq/SrmqI2n7ResmPIj2vT8H5yhj3AZZluAyHzMNUbAmeU5vInRqoFDN9/R7aZXCI0yYLOP/I/Ak6JVD/iE48T10pi7BsZNp7wMYn2I27vtBgKjx1CFq8OYhAcGQ9W06DLInU8gp0lmrKX+j/6kg73YOVxRnmntu1/NZseYLSbAGAep1+/GTVcVV0TeKUkFAQ3p9T+qd72F6Puj5Nrf66ymoQ1BACwLWkcCCwoWt5u+OCh6I7oqY8EpxoeELKfgsiCHjsZKQGig5P/rIsOjgmFw/qkgNkT3PYmOnISM6ASMJKSHSHi4EhMrHSPmrAklt8axoF1jvUN8OhwwmCfx2GAt6+50te8Gm7pkEMH3W+NJ++zuH+ChU6v5kID/yR97DTj3gxVgF84x3npF9C8M6X3m7toNj0AvOTMdDMI2YA/ZOjHzfDvD62XMf/YzM7bCsTOX/CHKklRaAIKRMBI0cFIr4bTR4IZUONBUVycW+9Se6yDo+E9WrxIoQ/rpA75FyHnKhc32s55tfzQTggynaaFPfN8KT7y+vyWpT/UmJ4O7FKXaQ4F3KUBlAGQod9P6/ubV0z+feYapa9/LKvyOjPdDEI2MLTjagDylu4haANSW/jZfh1K7YJgIIsjEaIpFAFPjrgzhhMQ6ZGegZzG5GUcJxTGaAeLRKpS02UOfr0WrSFaVvnhC3w340VdOfu617nEj0oFOlEB/8ZKZt/tgQ9Lhw5q/BvB7zIJqAifztlX1vG+0GTUnmoSOvQWPJ+W95vNuAMrqdMWMg+YEMG4+MXeI193BK8DKwcTZ2bw2CDYLzMmM/WRkE8UkIBTDYJFUg2FRWbSxIiScQITxwmFIw6OCYLVNLhyPyopWAH4UOuH0e+s9MMIQIFOpu9Gy65/kcPiQy0Aya6gAL0fRjksAEqkJ6CW+eyXaz75WyHYF9per6NPjqOZbgchi/AdqdVitwPQz38VgNn8OEwgQTAhhsJKwpp0HmDyU5ViSH4mcVpInMCV7DffxQM2xbqjct7KWp7xoU7AIh8uE7BIwCIO13uddUbNnxxY60OdD3UUOB/qBBhFQ5sv/SFYKHR/O4NfaDY9kL43l604jmG+wY9Fs+kvZ7ophGxhsGmOp+lzKrxnPvu4efAx6vheeFXjzkcgFCq5NA0JU5iJFM0k4uAYpjsdMS1x83jRcGwcHFnBCcNTklw8xb8rb7L6mmspWMLrnkbguwzr/sfRdpkAYyRZehgbBYscFjl6ldWH7Es2+Fo2gq+e+nvJWnTYYp//bczuw9FyWEktBUIss4uuDX1OJjvjZnXPQeWKmZIUc688rTORyDSkLKSAuqDTyOT6pZMn5ns4OR+L3+24S0fEnxExbF8D/7ADa33Na0CXCagZtdKwiwfAya7XosWB60aL2QOUCwAABToBSJsHgXy2LwAKDsz/lJJzAnMxpjRLk5CfWIe+TLVb/TBq6p53uq9JvDEZBi40CqsLWiRdd4IpVdGaClPpvo7bHT1eX7Sh/kEt/xtH43oBi2CogJpGdfHILGG1BGoaKGeOPuLAetBlUNPhBxCRsTgSHNIUDWyaP7k25xCW+T9R0B1ySS8Mp1FBLp2EUWg23k812cRBHOe7t0Z6oSN3wAVSFYuEv+NRiF3QIlPviB7LzMyUn0RAPPZ7PhIE9+vCCw+7QuD6sFSFg6fCSxfHxRUCdwyGhWOe58X5S46lN4OVA5jtuMN35SLnzgf06zdbW7em2ubsh9Ftsl96paK/E4CvtJZu7rHj4Uw3ipBdlOGXHlwUXgk0qjbW5Hqhc7QLmgh4PAq3C1r8OKZXw9PdNS0Sd87xBLtIeseunDiqI7p7ELyHWXwPPgOL74SD2rHlo8ViHerzRj0ZVbujRHZc+dlmu3O9Ap3Opi/4mlej4Yu0+WfJNzWHCLpKFP2d4etaKYh9CbFIT8qDzyrQ4ePL4Qqv+4llKgBCafyOKNILXVDkmIBl7v40BsGY7NykxETuUqfVxPKAJZ1znNQ0uC77kfU+XAa6DGrJuIs3RHAPjw2L9bPUNFww8z+EM1x8QLe3Rbe4xVO0pOfYk5pNS5w7H0hba7MAzaYHfKFQ2elD4o8KRaeAl2j0ePDtzDaMkEUE55XO22A7dQuC3SbhxxJJ8EzDnWixYdncSG0sUhWrkMmxLmiktRc6wnR0R0fInpTpmCA4HAFHuqCPW7V40WG4Y8S+cdcVjymbFdGwGAeH13IAhaNyWBTopKtZMR266IpDQzuaUnhjWU6FRbP86bKO98WfAlUqKc/3fPBqZhtFyF6oLiznKe1Hyh0HBRi8a9ZGOqJnpheadEFnIYWYhDWWIp9j+j6d052olXyKVsz3fFQPWPcgU3130XoHgET2FZ+PfikSE4trOQDRqdG0xgyP1Lf/y3J+fz7Zl9Fs0ix/umzfIbhl4kPm8fUof5LpdhGyGGWxofhxfd/LSk2HF2sz3RpCVkAEPMK03iFOd8r05Bw8At/FWp6xnrpfwb0MjG/fCHEdDETmLGmxW5yM5HCsAyP4cV0yzcsV2IGtAALu0sgDrgD2Lsp0uwjZi9bxZNFuu7XiGvacrYZ1W2Ee6RiL6ZqamfJ5hGwgx8aAMQ3DwNFMR2ZWDNOXqJVkctbYhCx0hrDgPKh4KK0+02XAyCKmsUSX8hm7ikMUPtT65Jvkznf9a2oR8iT9JnIAA27G6p8WvW4fCmoiT8rc/ZqN98t39XJ4LnNNI2QjjGaT1XmNQ3+F3/qysGMh4PZhERZkulmELIBEwHGYgcGS6YuGU5o0TFEcSu2olcBkhdI9snJwXPtijJXHBsrhMWAbAKb0Dn/DbMPex+Ee8XRR41+TfSfZyTzOv2kW2gJDQY2ACvEhvlLiPMlpH8xs6whZCLt8i076Ohh/OMPRUOHTXyP2G019kWBCTkMEHJ9pHRWOMK0aTvRqVEc0RXEwdCWSbgyJN+M9YkFK9Yr97PK79cEXvU3LDB/+CNgLgNFsGnItRJ05qRNlGWX4pXrTjzHvVNnbBwGJgAo/Kv2oBCCggneaAiUq1Doz3UxC1lFE2QFQBw8M166RoNofc/NaIBU5CDHkXha0yPT1Qo9lWnukI0xTsnSCHukJv/OJbs+j+6Kj6ngomvbqPnwNIXjqLgJQdvoQO/tuDfV/CIWcnq8Y5E8V+Rxm1WZ98MUcK81RzWHRcXww32j9QQhScRmoaBTopDVmtui36CMDeIRYdLjX3Xjx0JvLwgJW8eEOp9TrQpMs6Hwi98aAZ54ZGBjGtI0NJ5guPF6x6JEmDV8a4ph4zHLCFPdWpeaPvaF/408zHK5HNxTo8KLO9NFTEmlQavKVuQ8F3KWyC/rl7WbrvPuBnBFwSdPzA80G9J7WrH/asutrWrQBEJd+EpHDIqCCxhksPY3WzDWUkK3Yl95K4T3wHiCqsnq/rhAWKCT2TQDpgk6Wmbl/nI5O6eS7o8cj/gVidF+0gEWs89tySS+HO0DXgK7x4TIHrhsKaqQmH9/N2J3rB4LzAx+V+htMcASLGv+qxBOzZdem+G4ywEBnI6PZhMt7Sl8/pkWbF/WifYVhBw/LWMKceJDRbcIV7ZlrLCErMZxW7jgI2ODi4QrBTYcTGKPuYpPshZ6ZereEmSFXI+BpzYUej5kJhTEN0XDiBOkJQ2EMO3jcTunuQc3G7c6hdb6WjTBE7vFpuHhO9SC6j0U29AXrMKDDYrtqxz/s8//DO+uK2e04E9ie+nuaCarqm+w1G/r5T1nJC1Xmb/qCi72oF6JiX/HfFCx+VPLOPvvqK4feWY43SS80YRSGwDYgaJI+PcDPc5Tdhe5BLf+bcNEb07hlKQl5D4mAU2Zm8rMwDVOHJwyFJ4yGYy8TkSBYTTuPbcbBBeEhLvEBQE3DzYOuAcqBcjl6Nfibdu82xv49heYjzP8Ub/eeKfo5Vn06+Xc1nfQcbu5vvsQQ2FZ6watBZakflcP2LR9+AIAAox9GARWzTr2AZcdNK6/PYJsJWQgnu16+xGIO3u1Yegsj/LuC3+FYcz1T/A3q+N5Szdvo15F06MIkV5OwMLN5WHGZmeSsCOkKiJOZ5p84Go69WIjFLKNxRy8hHF2i0kbBAkCJwxT6aI05sEhhrbhGeGel6dz/MO/flvSbmHZMsq94Ahd7yr8Om5HRNEIVANDb/W8CjBHvDmMDQMFShkN2PFy55v/17v2vTDSZkCPIT+PzPgBwq5hTt7HOR7BAj/nHxpalTG8eVqaSsMgYcAJyOALO+N91xkJhkXRFw8nMEk4cDcfpLhPrU0ZmVqj42BKVahp0GVAuRpB+VBZJnQDMn/5a8BuqmFvMvb8GgJXHU3ov04d53R2ehuVFK9u1uM/rrOvt/je2e4uARWPsi8gzIUiwpKdP/U8z21JCruGfA9qDA0rGdTMARdNexfHtMSmNaSeDKdCEBOSwgLOEXNRwksU6knVwdDbWeCUqR6Nb3Gouf5y/uIpasb+45bOeg3/FSQMA7M98faByfEe/frPJvVVT1DbkWhjaWDQ87lsOAHRZ7AMQbyy8uLCkumXoo5VYymo2PZDJ90DIYuR4qcryMFNyu23xJqgCvpaNvgU5kIpImA5yW8AZD4IjzPANZro0PPE244fCU3Gw90gdI3yTa7tG2KEdLL8iqebOFLZ5dzuL18p63H7ZLKbru5JdgbB9R3Q7mqgni5u7wA5oTM8r2zpmrsWEnMKo/ZMkGLI71yubP2Qtz4DvEp8nw8AFSG4LOKuY+U6eqWt4qks4RDOeg0VcPHgPhaMKdMhhodAHYNaq500rfwvbuGPSpU3PVjR8K5kWpoO9AGat+hrKBGHHuXw3U/7Z6yz1ewe+CkRZVk2PPEYR7ohmFt7hk9b3+H82U80m5AClTc8CYOZtAMA6HoIr4EOdvP4sY7yVmfOo9vjjCcqq5zTZEyNlJ7k6DSk7mbF5StFQvHUq+VkJKnWM2mzsEg4ARXGjbtujq3PELNXAe5jF97BH/qNyzh/gktnXNBRZXWd7f4UTHwHj5l5Jm/1urChtOq+/+RZc8Q7e/HwKb2wilHhcJWs3F/0MCrlmxTNOjx4l+87uqS7nHy+58FPLh18Tjhgp7FXisENcyinWuMPPuHjQZeA9clgc9E3Kns3CRxcieE4am0rIdZT9Hcp1t7FtN5pWqGRn7mFPbwFd0/PJk2Jmokn6tMNNgwTAhUfOR8BZeIeVc6HwVOLgcScmRcP1MdV3wxVQ4CP76SvZiud9zbeZqR/MqvxP4LIEZyzFR6HGon6+FvO5sH21ScTi41O19GqmZoP4by/uCgTKFA27YLeUdbzPnPquyb0Vy722hkZJV7jbWaAvcdA3wVARx74Rhl+iNlo1617oX35u0cbD5fguAEa7aSqtJeQJNZyvtBZtQQXacSAk2jfciWKoAF1jXn87TNO4ZDgha8nhaUgRMj4fKS4zHAeLTCUUnvT0pDizkiJEykSHpyEBsCnQoV6xf6D9HAd9M6qKw+lXY6nhcN5xk/spmcfHzr4bLatMsq+YAz/AmDrMY5FufCO460rx37Nl154JbDfiBgteADpRpMRQTeQl4fKKot12ADJVf2CRwqzarGx5z4tl4UHfaO+K6dwRxOlV4X/zAMQ+dmHNBdg7WNr0nmKw0/rmkxM2lVAQrHtP0fauD3VAOQwVQNTHSSzKceUVyU9DQuozkcgcpOwk5yNgZOvfeIazo0WmEgpPMS96hJggWMVDLRm+2S8DysXCjY75d2nWvaCf9+CsVV+Lfxy1gAGtrMcNFgAYTaNs0QDWSHW4N7zBPI7GNqATdDfWvRe9a3DXlVrcB3SW47tnAj8EYLnwR4qmp9HgNA49FNnsTOCHnP/2geD8geB8qAJm7nFwc3hUjbJvdF2RUc2LenLY0wKMzOEt0M9W9h8WKJ0YBxMKHNmmXSbfLxXoBIaTCcT1GMTHco9jzXWZbSEhU+RDBIxsDYJFcigUTn8QLA4Di3U5xDgYAN+lQIcCnXQ1a1+yQdI8FGyi+5tvjj5AOe724CK/fo3JdtdAcD4ACn10NQufxGdbTKEvuJ6WhIJFPseQQsv5b8fpCnSNaoNm0wOC1KDo7/SV1vr4ejGS9h5Z7Gi4Ci0r5XipfNUOz75LijbZhZ0Vfhj10lf6r1pYtu+QZ9US565rY8OUBERC4eFEM730FZmqHwxY7aN46/yJj0DIfzqYOfewp+8GPRfVxQDCCyKJRC2LRCLggiIfImBk9585Ix/9ycXBaQuCxyMcKYaDYHbRPb7m1do5b4r2nS3/Z5zDMTUbjLjRpvvP0o0fYdlxAOKyuwIq4JZ5VlzsabzY07Ssv2RR0W677KiP7u1BiR1M7NizX1rpD1XSx05rdv2dOfDoULuaP2IqWfEZc/Rhw7rb/GsWy9xe75IvDe3U+VAnYJE5eFvp68fYgafkvHUkTIkQiVfGTqwaLWkBxoHg/IC71HHki8Xadyf/uyLkFXVn629Ub3or/FP0p2j0ooT5RDZflrOEPBFwlpMpB09Cw0kmZE0dH2pNrU9hvoE9fbd0YwuAAf+5UAnsnJ9Z5j/MqG8QZEZtS4s1+GUf6sLCHnhKvvOscsdBZfOHaA6KngNAyTnmSGxn70DzfMmOIb6bEQI63mnyodaH2oH2eYGeMk72NfneTgA4dJICNzzHt8ZMPQaAPnY6XNFaZKx042p4GGbOo/043xbcAGCwuT5dvy5CDqO3zJZdK5dYFK93prRfMj1ShJwmfwSc5XdbGRkSxqRC4ak6OEFRveGK0OJPQ0ENU3KjEoeDZ2qBzsCmUlPpdxj79zC7j114j+6zFoSXGyoXsMiL+tKG132odWA9h+v9MPpRaZ77xJC6TG/7CzRBrDyuX3+7eGQdtgBSzbq/+1DrwFoH1vphFC0+FNQwRx82av7oN85WNH3kwPrwcK+aRnUxqotZ+vdh+yYUbZxym4AWL/bW32Co2aZaf8Bb/hVgyZR+k4T8gPGHQlJJ85ArcGnsS3FnDRAKhvwRcE6QHw5Othd6bG0BvovR3GNa8YwShxXo5Oh7A2UKL/1POHTcsO5p15tfx4FQ4KNS5tR3YamxH1nvwPpICSoBi6TNfDiPFOXiAkTKk/8T2FcmY129dTegzELttuqwRb3px/a535614rfiOQUYxYf4I13NQi0LVUuFNp2vec3whBBJbMibQL3RjN7MMf/OCvsrKIL173fDdjZqteM9SR2NkJccCfRftdCHOh8ui53PNs0loAlZTl4V4sjIIsGpIuYNzfBJp1isY0qMysAq73V+XX/gFaWmAwDqvdgXZMo2e9Ytpff1MAvvsB9ZD0De3WtY+DCHr47KRnbBi6tHDstDCP9/lrL7sL5nu+yCfvvGBh9fP8vxjGvhp7IDvgHnvGFhi1gcDVeWuE+aBx9THGz24bLh2Hc43p0cKh5uGmoJXMCJT9wnPu9ruNBw4mGOvu9M2ZMw/Rodx6Ub+4O7Jnl4Qm5Tw5lMd5nbHgW6AHHGGg3Qk/+8EfKIvBIwiIPHJ1UHJ1khK5bx8p+jkKn62bpHIQlpyl6Szfayi+7BkKTUfcx1ZJUPdeI2JbtPABg1Ezc6dAgXnwo72I/KEpwA4GM34aDL2ngNWhivf7EX9WGFAwAE3giEcCCkCDb7UJfIvmNLacYQt3CgmgYW+rhyRcserJOhzW1a9hCA/o3nlu0/yE5wREJ+ouz6H1nQbeAf9qHWhzrwNnDlQAVAAyoo3ejXodQuCAYxD8sfqpxSniMhpyBd0JkhJ/qiJz8YHLGvKxSOfYdHf+WwsNRv8RanKXup9PVj9u4rmQOPYUDr2rRajl6gHHSND5eZg99XNHWOdA5HPzAsY7osEt26Nq1mqa1g5YBt6OjnmcrbSqQnKxf/cWR8V00D5Sb3VnP54wp0KtAxXBgkyr4xiyomIGYb8Qhi2+gyH+pkA26q8VNZjxsHQs53b+tx3MWUb5zkL5OQy3iXXs0vnFO00u2ruAbQMdWPKtEKF4/uQbjpmSkBnZGrTZYn5WQJeTIPOIbsD4JFZj4OTrUjerwgOGY28Mg84H5duPoVgO5BccqvHL0OrKdgEeavYfpuDCxSDLWrBVQ4sB4Ao/m511nnaVxG7zjjoxsjka5p/rfM1sfjN0sMqYelLs4qdjSu1wzupv7PJlP1Bzwl5sAPR8W44l7cMTRITa1PYblEUOqsp+4PuzMZ6cYl+gIa3dnOdxmwTUAFhb4iqVM2x8PKfo8TpNpvYaHDPXasF+utajY9KN9p5jdVuVr/Hy5jtS1vOAx3jHTAGLqQylTglG6miYCzFhIBZ5KZT42e3iBYtO8Hp9A9KNrXtOIZ9Yr9XtQzi3+uXbeHKbmRd5pwIMThej8qQdeArmGdj3hRP3R0pRy90SGv2fr4qNm3YxOjeA9go2ABgHVSAMWfdFmDX7Y71yMkAV2G6uJRPcxqCQwLcXCBOfgD2VGfde+W8HEmbd9E+5ZzuN6LegEVAAKzVJj/qRJPTP5EhBzEjmsj1c6dO+/n8Jzcz5mWbTG0/NGB9eE4+PjHGtML4GowfCMbt9YNIS/JtzFgkZwYCY4ww0PCaUnIil4cKbYMlmEh4/+m3bnet+bygb3zHLgOdBl7ZJHyyG4OmwFogy+CrvGpF4a3d0HJH/aeQDggSJycEsl46h6kcFTeZPV99M/6z+4caJtXWf2Ct3uJgEUCFlFXWfEPOrx9ZEcAKjFOreA5ExYUj6pGFM3YySEpZauqabgAPjzbyhy8T698turszySGIW8ellsgJCB2Hpr1zSeNuInWmLXOVi9fD0BYutzZOhdqOdhzsezTTDSSkDHyswtaJIccjBnvjk7JwXE7omMFLCpK7H8+btXiRS/qBSwK13SMwtR3l3leVCzoCoH7BJDAsBCMP/zkWC9GOnuHV3dgjLfCLfM660IbZfJdFg7XhyvdR08oGnu06E7j6OeTmZE5nobHtC2yMANz+HvQBlynVruxJf6+hALDgJs5XG+SPY2QZCio4eb/iDqxV2iYhZYgls2FoYuiuEgvNOmCzm/yMwLORbI5Dk42I1r0kCsE2JSLOxxH1o+q6Sjq0E2bmZ8B0WEuDddcLf8bBxYC43fqis97VVDxcMlNy7bAIwv0lHpRL+xaNGrthLjejTlONMkXQxC3TCIa1uJF5eIO9sBT9oYGoblciYPJnoKQ73B4DnjOXMPBETQs+xE+Axplhh1/4gwPAuEb0EgutF9mnLHidGmE2DdJ8nkMOOc+BJkqmD4VRsJfMfZ10+D6TNKnA2UKk/Tp8Jhu9PCt2Osb/VDxqJVQ6+xg/LEhqfiIRtxALTG3f58deH4oqBFgTMG+MUcee/xkGLtLzLmqipXVh9izv50d+k9f8wV+nG/HoymfhZDXmCq/Jbe/Jbecmd11u/DOSh9qwfWhMyQOBidmKot/E7KKfBYwiIMTkravcb8OZr3ig+Zw7hVsA8H55vZbzdRjsUaMO8lH6YbSzfk3h18a68W4mqTLtCeeKFrhAhCeUBQ5V/RZYlw7OeOOJfFxqvrsSzZI+joH/OcWN3VhNQXZZyiPimNWf5yGNhCyGzm2x31eiScMuNla/s8V0lcl3SH/Wr3Ber8cvQY8Cb4LrDw2qYKQv+S5gHOR7HTwBP1g3YM+wzXgu0zC3YqmznCGc3TpjLihbUp2jLykdANgzr29RHrCPPiYFq0UjoYXOhxvl2ki+vjDb1DO7VVo/u5rXj1L9hsgMHj6izCXo56C7RMjbjLJvoI1R/EWycXKc0qbng01FqFhf+wLVJd28f95m5ZWvvUHV3AVAOvAHT7UOnAdZ3gQC86juLfiHI6Qp+RzElaE3MrGijAzQ8LJjwTHDAOPSsKKdEG7QuD6mOq7WcszqC6OnWWbFiOK52LlcPFMye2800QvNtuPrPfhMixTjYTRyTF2GbiU44/o4l9uGsetjOaeXufXJQgFNpUE3vkcLrAZ5E9xbbeVNP2DDnaX2o71685VtHWe4V9J7USEXKKTqd7Cnn8f3lQxFd9jmWdxqApLekwl3x1qV4erYgEULNp1e4p8DvMHj6K6GCYrSu0xs4HH3vtm+WLAOdfvmEEKIgkrt2YlRZiZtKzks7FiUrGiZyINC08F0HCV9Xb/GyLRb3L2HW891Dg6FI/DqMBIWPa3oHnqyLtolOHNsgnfQjKrrsZsk4KPlW5AhQV61vVbGACuTxPaXlb6LLv3Nbp6g0n6E+zEUFAj0/hCq+XEvnlMOe52bjrF72QM3f/ta6rD4ZCh4mHu0mOm4N3msttNK57ytV8Gugy8RwCENh1XcT+Q9PofKZKLmSWFQ6F0QefoTdnMfHmmOBg8YizRtXyXEofBe0aVgI5nX4riIo8EB4+zTak9PNA7PONI2LFwpLTk6HMlc5YETLxvTEe0igfjh1oCQ4Vz17X2JRu0uA9u2UBwviu4yofagLtU+drhSnwDAFklKS/x4CJT128FVPhQi+agvetKoU3HHPmuuew2k2ertf1L4bRBukyLVgEG9BkV/I5wJTUACcNfQj5RKALOXXLCwcCwh1whChYHfqLFi+COjXopcq7J6nDUXuIxVTxz7u0CFmGBHmsApTtyrqlId+KzxzD29kLFQy0BXaa2vq1cfMSzaokfleKaxLIqz5nAdlmRF+iMlEki5BN+XMseecCPSnEJQh8u86Le4vwac/QRc9ltgmF1uEZbdbHDcIcXF1KNr+o0rYz1BnwcO2mekN8UxBhwhFzsiBaZgb7oJDuio3uh49TiMOvRPUjx7wowYsF54UlHUVJMT1OjS08fKIOaxvl90RZMo3cTnT2GmJWggMigOGADygEbAAoWJQ4XrXdQXru5/UXM43CSZL3mD2Lys3r9XmfrFwQsCk+F57uAEOi5IxPzEFU8nOujcLRS89/swFZcfnrCQhzZXIUjR/saM0VhRcC5++GYgW9RemYlqfjhpYpqYgo9pjcYDf+r1B5j3/RGveOdfYJTRKd8qyUwVICuiV67yY9KSWtAdtRnWHcb1HycXFlCjjKPkzV5K1b8dahVOzJDXU3DsBALzkd1MRj/qDnxYk+JoUKAMbBIOWriwDiQScD5RGEJGDnu4OnW8JS+21H1oSqr/wAM3+CX2jGRfeWS3vEe4zY1YsFln0ZOMd3qjWlA7FNjZyVFFoEYvX6iHL1FUqfF+S/+YiMM3YaW/2bO2TAjrSZML4aTPxgKaXAg5Edl5H4rvL5IpBzN2EI0aokB28zt38f5fTP5GSZknILIgo4hR5OiRWa4YmUyjHTJelVw0wpuO4vHgC7gvMQ7JrPweGSbuEvETPpqFa7zN7VlZyiKi+2OLrWPW6hSTcPFAzYtdnMN/+J4bwFsRr/zE7x9HgfgM9DYxuP6qbSHkHE4PIcdz0moa3SqVmqV1dl2w6g6rOPVRnXrHVirxYuO0itAMrAKiUIUcK4zrQ6ecFZS9GSkkZlI4bFYOTirj25EdTGgx/FPoJobmdc46iBJqDfBLsmLM/GJxr6aqpLjODgGcfmmKGiNuZg+MWhbBQBvj9yjEPvmNLPl/3zG/xfx32dmbYVlULvrN+HJeGPtG91Z0q+DihcWrHHMr0x7+EsGgLOcAhVwTgfByLSDx2KQb6XP7WEHn4daDwAmKwCYyoHYW/5JqHcsaTlI4iNPPjiOBMFK96iVl8JLJFmC3uLBI1cZcDOH56bYWkL20O9fOPKDJ6hZ/zthp0Gz7gXnsc1AwtnwYgE4AyasvwEyAJx3FKiAQRyckFQdzNnvwSAd9u442cjTZ83pIPk+6gmC4HCmK2/gH/ahFoDU5NMs2uq/wISdcTZntJtYR7wXCNmNY+W/wXIXFAI1513hHUlZx3uhuiLJ6ZAzeqMx9o0JeXPrO0KYOoUrYOSFgzFtM5QSODh2dcJ+Hdw0VPx4+Va5e1kZVe1rfCZwsCsE3sPpH2SEGwNuBQBn2w1V593hjLct69gJbS8cUxqcJsw0dLcptAWfhYaCGtrBDi7Z7aE/J995lqPvRS0ftzxq4q8JGf0tEApawMh9ByMb0rLMenHGUV7ezk8mXSs6Fat7ELyHwlG97RU2uNO04rrBsnn68x7taW2Ou6sW9zkcfVNtNGFmqTrvjp72/1TgiB9GofsaQ/ctcpwVUI7q4tFLX4fJp5vUCGQAeBIUuoBBHDw+yQbB84/lbldzkkwYCscPgt20gt/ubzTpd70iU/VjoNus6saArkh9DNgKQL3px66dP4hsXo67bQ1XAs4q65cCJaVn9/1pGt4KIf30GH6AJZX+Q5yARcBHHF0GvgvAyFT4hFViYr4y44W/ZAA4/yACBoiDx2dCB8slvXL55I0bc62JWXApq0g5FPaqAOg0rd4ddWbciorzcYJDi6aq7h5u71cxh0O1fWBnR0nTCwOdm3DSwMzZ4DxtNAS2FfkcPR+8CoAp38jadk3bGyKkA7rbsPqntuKzBuWvz+KmcOUNAFgItWRs+DuTsS9ZhiH7IQIOkx8OxjQMCacwGDw+SY5pJT/0lSlVJwiF4wbB7MBTQJcB2zBXWnTKZl78y54P/1OL1qILXf2dXzJq/tgb+nf5yb/7AbbmZ4zqe4HdpTJVPwAjvhmazZWsemGg+YZpf1eEybHiBNN7a2B3mRF/ltYOAOVh+0bm/k60OOZY+5LR34KCCHiEPHAwpicUFvu+4mo4cr2IMeJ0X0fGO/4MiDnJzCxATL/qMmAbN/9H+DCEuRJ8aJFgyLH0FuazzeXCTkAm7CjHvPOh6sGBIKv6rTb4JOXsYzSN/MVUj+0p/cBP1fj3Xvx+mt8TYTIo9K1DUo37s8/7YRQ6jOGyz9GVN0aTl0kSImQAeHIQAY+CODgBiecmZcmd+8z0aY/n4JggWMG9TG20+mFU7NouR6+DuXnWpY+6dStCOI7PEOgpGwjON+DFIqNLdtRn59fDCi/q5bAUuZ1FPgcOMmTQL2spv/LbpYde47sZAAKMI4Unp0CCLxEZAM5LiiTuQEgly3Qzsoi8cTBmtjs6O5k+H08cB7NyHy4zvf5jV3CVj2708R7q4AFZnUfxdkc/zmfpp0BBwe/woh7t4hV8+MiwcBuurzz9ghE3WPBCuhpMSBtXtGNQQ7t+DPfkr5z5FP4SJk3BLcaQDHnTnTIdWRgUb83dm3F5wCI+0nO0eNfQcDdjqR3n92GZyjzvCZ/hGlQXY4FewKJgmYLD9RSspou+p2jY5YdRgFHAIqAcdA1QLgZSs7qe71H+grrQJh7TgJvT0mBCenhTBcD8waOeS5cVrXAVowt0DYWj4Lvg4uEKTe6o6e1DmskMrLy5YM48RMDxyZuP1DR9D3PXwSIRE0/xqpcojim1Q+mGyYrz+8Ir4SzQm7lfg66h1tnNZbfp9rYAAMpBl4WHD+kyAH4Y2dl3z/7HvT0f3qXEE2jY599YiaVsVfGXptJUQlpgajYAtdi7CHy1s/NWc/uLHnzOtGyLfsWrgEbB7wg72D3xwoLJk+tfN8J4EAGPSz45mITCCZiihsc6eNRyxWIGbGThORUPNU339qAlYHH+S+XiP44sGSs+6DKxO/rM0A8Z7S+9uBzvzi/bd8hUsqVn8NVJN5KQLtiu10Z+OGUAYCz/b3P7i+72z1cVPyBBMBwHdw/CTY8qBk4gjEEKQOIOZLoZWUreOBjTGQrnk4YnZ+JEDsZoDQNQSwZnMaDn+unl9iProabD68WK2bNqGnQNWrSAHSEY1j2tdTzFO02SA0OlTb8BOrGkZ3JvkDBNiHO1vagPLqVLcVSBDgW/wyTcHXYwMO4KlXlBPl0kZxiJOyCprt4AgORhJSAPcrKimda6lbmVopWASaRrjc3Jip0WLF6IxahovC5KcQTRxYP3ADYKFgByWBTolGBIvpgLfkq5+DVe3Jlq8wjTy6XH8LYAWkVd8b7+tZfM6+4wtPyJw3VYdg6U7rFl0pOsfiWS6j0uGQDOCYiAk4U4OFXyw8SpajhuXvQoDUcHQzH9kxElRxwMRDQsOpjWmMHAM2eJfFdvAGobHkupeYQZQI7tocaiyrd+H7igzLz/PizQw2SdioAn0cNEBJwTSNwBaeRfmW1KliNz9+fT52wGvp9i13Sud1Cn2iOdKC9aJNIdjeGB4QjiCDEw0hcdj0CZQpAZuaXft+FfmTkbUmoeYdopt/j1ayo/fCHgKZWxLmbxPSb9XfCqxBuvREtm5Sb5dFWcYUTnkkIcKZAfU4RFpnUpwxjGOjiH4mPRwcmHwnHnB8dWqYxeLkl0cMJsHTksauk+maof6gDeDWpkf/OVfRmoZU8n2SjCpOkEalPYvNaGo+V8N0NrzHDLeru/rsRhLKChnLYGEnIZIuDUyCcHI3NLGSYIi7PTzSlpOGUHA1C6ww5W8WOHh3XVb4Q0RUOqsrP77gFqEQBsf5nEuyAkjxwvlW762HlGjYMp7FX17vd7TM/6rLWc83oAoGsc1WvilqWcPsgaDDnEyDQk0gudJHnW6zJNk5QmTXTfdbb1YyffIz1xXzQS1egHRuVhseXP9Wh+yu+bkzgaq8Q3kmweITGlTc/68RX5IIcyT0o79lz0U8Nn4vqS5aBrRo0jjO6FTrKceJZ88uOSZ1fCmSRiWzIPeDLk3ycv2zQ8lizx8RQLdyRycPR4MABgtuxBpvpRLXYztptRaiuWnUx88F78Tr/+9qk0jyAy0HwuU72BPnZaofl78nsp8Sv5vg9pzVk5egHbtLWOkD8QAU+S/HMwcqrzKoMyTnZpxXGKZCUVB3N94LvO1P/CU3cRh6+wA78roY9JKWHCk1pbnzKtvD6Z5hHGg9Fu0l35Gt/NBHrKNK/v0Wx6IMkdVSvf80PPOr+trD5kWvEM+K7wC2JFDjEVq183ralYOfQVJoAIeCrkWWq0SPaHwmOZeRMnWbJjkgX3XSEttoGukR98x/nJ5tKmY4zyGwPNNyRZCcu8f1tR418nc97CRgqxMmiHz7E4+EaJgIqB4HzprH7nzgeq6v8pmSOY928DLpOjF16pWbUZ0IHrC08qc9Nw0/i4Ah9XJO9g0v+c94wSMBkGngR5+UHMOQeLzLCJJ+3g2CA4BrXEQd+s4HcEGynjie/3sxvYU3dDl7TLVx4f8uuBI4x2U7K7FDxybA82KAFUNLxg19/lR6UX9X5U2pdsmLXqa/aaKxPvbsSNkX/7Yehd9e/MgccMeAqwhctSdg8aah6glu9XcC/DnI1phoQZI9qzJAJOA/nq4BzVMGawQGZ6VrAZ2wutpv0wGvq2U3UWBbMTV1By+1tJHky3//cKusOw7pkzjjuqll5dju+iOqHvCYAf15rcW03Sr0pbeMr6lg91Aow+1KE5WNTtBEIAaGwbb3fLkgeiftJUHn4h4C71iRlzvAe8R8Hv4PruFXYs9NGN6B4MnzS5PKzkmbEvbF5e8TICEXB6yMvuaORsKCwyMxqe0MGTCYKBUGNRSCp1zL1Cd+g1DfWmX35Jku2x42EqYKGPnZ69+Km+g1fb8LOKRQ8luW+hMbLOY8N+l/7SoaDGgbXh1SFRDpT7YQz0lPU3rwLdzc//Iioss4uujT6CSfYVaHvBUkWNzQBKm54DnOzpR4fW6rR1bQp0UDjKaO5Rr9gPjxyGCtRKsDy1zGpCHhMrYNILPRXy1cFEw4mZnINHiFesn95xZnDfXHmzme9m5LwVfhca9k/YEka3ianZACDQUxb4qFQ/5yXmnA22libo07nWbH6gxX2c/F4af54tuxYhicq2n2v4FwHG8OqQdBkALXabz3kCtIop3Wwy3EWt2H9maDsAUF1KPGHEN831P2eqbtJ8/hkJAmjYL4T0aJCDVvXRm7zVF6ql+7TY7Vm1xNW+kjqxN1zjLDmyeQCYMGliDBuuBR0NqQs9RfKpUkcMGanakUamr8rHhDU6Ei3VMHaRBlcILh58FwWLEoeVmg7PqiWSXUMShOx4eIKmrDnKHP6e11kn/lQiPSG7oN9/xGjBb1N6R/mKZtMDQzt1Xtwpx0tGzZ8DixTmsjuruMd6On+lFZ534LrwApGIlOPugn5hlekm/+FZlMw+EJhPwRJCEdbJ6N4evrJKaNNR6KMXmwNHS2SzvXw3w83/EVwhw4UPOkq+WPnW73nnbA7Xg65BdTFUvLg2g9gFErkzG3sPl7ULMORlmDFjxAiYdEGnn3ztjgaJhsdnqkGwSMS+YcoBlEhPsPU/db57mwNfnci+Bw24ucpxD6ShSBrRUFDD6n4aXC8HoMSvAGDl8YlbkqeUNj3rFL6oWvEOU71Bgz1eZ12/7twqy6M9qoeoKw86Lv0XGCpGqmeEV2iugbWv5/C3HVgruYD34kKZzFu0wl202+49Uudou8yLegAhicQ890n7kg0OrKVO7FVYX0ZbUN/3Mlv/KK0xi6taRf1lCQQgbilKiTtAguCpk2dFK6PJVAHLdCE6OO3RsDxgmcQihmG8KrjpkQt0uAwWAJgv/aF275+Vmg4WOyc4yFK9fKiX67hegSN+GAUYBRgV6ARQ1O8E9nh15xuWHfeVttG4x770Nu3BZx0orOHh/uZbjPgmZMFAqMyHWj+M2AUn1jK6Lb32f4VBG6+XmAZouMoEHo6Odf4Gk+xjL6+YU7Tc4W+vFGAEyrk1F2BvH/CJ78QaAGIVDgU6zdQPtHu38TAJSz5vOPQTB7dW4Iyga1CtF+ZPnAdAyDPGjvCSWtDTSH47GDneIz0dGk7s4Lg1otGvg1cFVq7lnvCiXsCiqNdscljkGquDvtnhPIUVv0L7fPGFioa7vC1Ly9a/R/ns5mO/Qp+RqdjYW/We5MMhDfY4sFYUAwAO1zOnvssuutcke0LiDUiPDRT12GXVHp7Z6ThYWPYVseC30FmY0m/gNMK/JbqM9W2FgR6xb6R6s5uGWiLeGFGwyNGrO/CYZ9VFZZ3vs77faTY+jV3loMvwMQ1DORDptYYACKgwyJ9y4DIBi3DIBUCJw35UoqFT3mx2HL8OjeMKmPQ/FwhEwNOL+HklGs5aKN6aqeUfopdnUHAvU+jTYjet+e/AIqWrfaUfYZHr9raol71tfv+n0vZjwRUn0D4f8tN9LXOoxlcDO8qsDV/V9j3hQCe7zC3fcZoHM2JfugwAeLDdW6huM7fpa/TObnSLkdkTcNiw2Iwjpoy89+lnD5YswNEgzpfhw9kxr8mtfw/VF/l0/4RDrpERX9G+MQsniGtjqCVwQVhzgWPvNY5zWMOun7C4W4GXS9/4xCnuPhI3i4MIMPBPFq1wWUuuEVAhnqKoz8lVPAi1BO+E5HgSa6Lak5bJbIQcJE4SlgjphU4v+ergCDmtYaQ1FE7cER0dBIcF3K/DxxVybm+F9FUsl5iLfjh7//39gXPFbUqkJ8zBW7VodchvQiikFHZo57zpWXxRWcf74gb8wjmc/3a8ozCseRSA0KYLZxJFE+7QtlGwaLGbW30X2HKcGlOPSW+B1ahfv9naunUKv4CMIcHfTLJnzwS2A9BsvL/U/mlf+9V+XBu73XwOJz5m5jzGnn4MEYMmWLPITeP4J5h3nuLkdh8uA2DAk5zhQQBxuqzDCXQezcbtZfsOsc5HYKgYOb647BUrB+OnZv9DzAyYYgYWCX9zgrgzjEgS1gyR95/dnE7OQlpnfUwmoFFLNNhtDt5m/f/tnX10W+Wd57+yfHVty9aLbV0bJXKckEwg2A4kUNIQkoZMTCiJfU6htLDL7vSUQgsFZnY7BdqlpdNCW6ad7RAClELLLLNlYJf2OITWTQiQELJQSELsYAh5wVhG2JJjvVhyfHUja/+4sixL13q9ku6Vfp+j0wZbL9f21f3o+zy/5/cc/BLHbg9frp3oWl299lR1xUntwonKrvFacx/aKkxX/86/ZZX9s8c0u87Zh77rH7oEPq3z4wd0+44heNp5+Ifs8U/cljtnpBJzi6yraRDQJKCxbv/LtumvJVZj2c7dYtr6Q9fuHTB+BmBhR7e4qEktLLj4Ec/WLwD9ADzem/BW2Ni5v6b7ifj7GXhb+y/HP7kmsmVCrERrffE3AAZejz6ccgW4L2OZBVyjk/sRjBrYgjDwc27isxlZAIFdbfbJ7QwGIvYVn61mHNwgLhwDNzjfT0ELkMqHeRMwKATnB4rCCkeuKDxfDpZOwH4DjoUBgJ8w4zn3qttw2ItVRmvVdwFoB/x2z09hsdiEb9iX7MDh02bsEYtvzdjr3HIzEEavAPb8SNLlGoEYr4i1XV4e/ASDgVocrV57ysHcj+FGTEPMwYaun/l2Xsttfpx9Z9h+9lEz/+vw1krPrh/NHvclwziyUJbfjGy0OyznPXh293I/7op+jdt8e+XecQDe6XU6jFR2uid2X25Z/Pz0kL7CPGkf2wVmEIZqq/vuc9MmAY0A3Mvumo2nSRDXiUV3bo67s/hFIFJM5xxjMNDc8szIxV8XPlgHqwuYbXkWuwaJ4m85MF+DDZoDLjQlXJklovaJ4XzPCkuXYtX60CYWQrNu5436wz06jDAN4w7+B6g5o/FMWdc+qB0I8B6r+fCvg2h2o1N8XGWFxzbwEHza0BW1jje/H69ekZkyIuCMpeLFc9Mm7UDAtO75utC79sGXAWCJc3rnf9ha7x2p/rrZs5vDQ5UVHscHj9R0Wyd7ZnpFDTOwOXX214Md69BX/JnjOvzC3/+hZ/EmwXIFXFEB9/NVPcJ0vfjpJIgmpvqMEfvs4d/pt/7f+qMvY6zfsvGfPVWbMKYZb7ya1Xym7zkaeWicUGP7g4oLtaN3kPR0VMziEDTO6Lce4/lFwl8uRRsffcJo5XOWG3UQpQUJuAiUdmWWiKo1LEuBdParkti6AN+hwwj7zjCW1Or2vxdEO4CQr0ZAox8roxXOYOsceFhcYgptAFy9RCWRb6bCyAsGo95t6wN/vnaB/x8BjA7+V+BlbHkTr0yaTXvsF9xn3tkrdjCuN+y2Nt0dPqM9i1fDuAp4D3Uaa/PdjiX3Q/cpAGPXQ4Y3/p/dnWpllCyc7zzvvP/22YF/1+GF4Ia2pn3/cxS/MTBvV3/hNLt/2O75G+g+weVn8cYFWGVipxwefCH6KxJ6zpgqXscFJ+r39fKXLcIireud+9FQMXXy3XOoZ/GpgLl/ZcndISW/GEvU0H4DjBrT556u63vXvutlDreZ8aR7puFlon3VEn+JPJFsDpjaUuaVchjSUXXjjqJNxfGDevS5l95lX7LDJtwRbLyK2/yYx7LBseQRBmOzd4ub6P2sITLXmFjHG5mbZHUYDfRfb7303uBVHK9t0WFk4cpuHFpq/dx9IZ9e3/ueHysD6AiiCTYEGlZ8FvhZGFeZtj2ADTrb+C3aYR+qvKaql43bfuL9+Gv29ocjey5dfRDMIHAMOLRAe4OtfhuAqu5/sy2+FkAdfgkAzCAqBxforo8eVwP+EfUjaBjFlR8CaMKtAPD5UwBsi649b/3NADR4FUDFqSOfDf2Loevn3MW/t43f68RXAFQYzupGP/V7Ojg8aur8re6NPlz54Xm133NX/23Dup5IEThbBzQ4pm9neh28x8qODKN6HO6xmov+UKU9VdP9YYDtiow/i6E2pWjnI/rAWh8MvOevt9uH7tHhhcoKrxs3xt1Xjdm3HC5W+SOJSZPNAYOmgQtCaUfhKCpNwznmYMkQHB2CnjMH7GPhDZud2wGEt2rrDhzxezoYjFVq3edCZndXJ7vTHu5m0DMdRJOAFYhbAJOkiFckMjd5XI+++vbd9pHfwDVg1T7xWei28Ba2qfff/OgQS3wB6PGaseKA44of4X2jbnxfcMsCc29vEM31pt12z4O2hvv4VS2Ve8e929YHhrpsZ27DFPiVi8QOLYGatgDfAeciHPbaFt1j/+TlNH5PR3X4MIiLsMrILHxHP320ZvwjANqJwMSiS2r+dNxx8T/XHv5DDT4A4MFVQdxgW3zt+MfXBNGkw2iA7dLxbwWv7DC/8SygiW8nyU9w2O7EV6BbwgUfcm6422R4wfPSdWY872ZvRZsmpX3jOmYk2803+qc84TJtfcGzaxO45bAF02w/iUw+9hXyoy0JOBdIwEqnTByMstRwooPjBexsna3c6TpuPvtK5d5x1/R1AlZw2I7NFYE9K4JoslS8OPnF5XV977qHrvZj0+x0b6x6Jacn59YHMc79ze3P2pkdFsvDrte+o7/m5frTvfaPHgU/CLZ19lH8oHXt4466u6yv/MoRup/BBwKaGIxqMG3CawIag2jWYQSAmMsrKzzahRMwavnmhU7tTZU6l3ann8fNGfymFjsBMO0HzGdfYUeGYUfIVxMtlQqiWVwbbcZe7boJ34HPz3xcOGPpfF7QNXo+vMPK3e06+CWBWx/5zQDib9XWfl9ooBphDQCtbSK0wHDuoFFAY217n123I1KcPJf021TF+3ims4re+X8CWy7Gay2Rz0lLjzOMMzb+qmX8meybI9kLGOTgQlE+DoY6NZy1g5OEYEHgMFmPt+oY/m0BK7jNOwJ7VhjXvuk49HMYWXE/Buvaxx0H/wcAM56rbTliH3oZllGza7ubvTXS3F8keQUvYrpdOo+btu7V8S6n9iarb4d2wO/3rHSjE2zrbBtkAM4xNDZyYz8U0BhARxAXzXzjjPh/Yn9jHUZFDYsrpkILDY6D38Ql5+dSMl3T/UTD6T/BDt5jFU0vdtaMuUvDnIXO/CBWLYaPhX1qblsMALMLc4EzZux2b7gJ+8YAgFtua/qGvf6nwJz4m12HSImtNew6cbwhiCaBW4/VH8UKmOJvmZB8JpcErCxIw0pGRgfPEbDDwpzY19zyjH3onogFZxRic93i93S42VtnfDzBYTvb7gjV6R2Hfj4r4JT2xUwOtusWNt+q8ZwbGfo7/dZjnl2brBWPTX5xuWfXDZF+ESLRlUuSzLT1QKRB46gOI9UVJ7FG4zj4HNAPtGf0+0nEgJ8bW/bzQ7aYujNES88AzPms4OUXhL7zqfYXaKkCpEbjxU8eXl7P7zRWHHAwD89uTIT41UFZM8fB4sedoSnr6nu0AwH7+TvADUYFnKN9QfFXPSQXcOpGHFSKVUjK6nRXXX1W1pspJWnNYRV+wlx4wIy9/JBNjz4Y2dj2DrzH6l52F1qqYAuipQpcoxM3wRvyHrwC/EREk1H71oxL30TEu3n5QF87jFqBW+/56+3gljum76/584dz7Auxm4QGLVVoqZpT6iUSSZ8Ns78ZjDmu/MHUwdbKrj/mbl8APtxjn/ytc8t/jvmalH1njvPTxY9GnCo5F27gxZ8uYPny2PSXIl8RR55lsq/EM3jDen6n4+Sv7JanbKfuiBt/zgXVvXHKlpT2TJ2AQSG4GFAUVjLZReG4HBwMNwsCh5PL4Q3DFhT7HerRF9iwUfrxfgMO+Wzt940v2SL0NJrwqhubhNVr0q3gjd11OBZxnZKkt3yxKTNmpybMhmA9+nQYqW054h7qrNry8Vjvr1IcRtpwuM3ddXXz/md4j9WJm2aHnWNbN0dJZyLcx+LEB2AXRwqv0lbvfOKUWM8dHdXwG5hDbwncelwYKVxnjB9S/C03SMAqpqwcDLVpOAsHJwoYieOWUgVBke8CcFgwNGXmn6xee8rh+lcMTWHNBJD2+hnxSeJIVHIciRqedfAZbGi0Cj+ebFh+9qXzMyu5Sos+M553L71bf/KFADZKCzileqOIDpZqSiVJ+oE1vrtZrIBXf5Sy+FmZ9gUJOGdSCjitRhy0Q3BRKId+HbFEryyqMHEWDbPiWnOILbGiAhBqknpU/JYV8DbWNvXZ+R2wumDNcOlq9M6xJk5eOI2ZHYGkYDBqER7XvCvUnfeOBw9kcCTpYdr2B7fvP5nqdggnLcAZ8NEh6PTsm/jLqU2r3irTsWLx/qKGGcYp+FvNh37tXnYXrK50lh6lDw0+q4h0Zm/TSsCgEFxUysfBsahCw8g8CidZlZTsVaJB2dkKIDEoz+eSZItWRSRjsUishqMOjikqZjBqqXjRtfUG4S+Xgm9J8ULZ0w9obKb7xj1XB9iu2SK1JBXgqT6XSP66cp+jjRvVSLnuCBR/Sxc5BQxycLEhDSuW3B0c+XoqEyfaNKPSoRQyTjlALTrYG4aXt156L8LhcweNE92XNu7rmbhyteelB9I/koywmbaNem4OLv2C/uQLqQWcVL1yRd6UBMPNcc+pLvuCBJwbaRYvk4DVRHk6GGrQsFwOnnOfNJJxFmSs4dg+HogImOHf1nW7Au6NmKirXHj43EtdeThSAEDjKMZG9d0HAgev07n2haERuPVzBJyGfZN/TMl3b8j5hp3JviVMmgKmzRjURLnNCkdR/tYOme7fIF6Uk2s4Vgwyyli00bwarhlPNigd+zzTTrxfj8tONLy0a2rbj70v3S/XEc5hrMnWei1Oa41n9rtwnYAV895Tyr5FVG/y6V7a9JdARgkYFIIVQxk6OIqSNYz8RGGJR8nk43k1HOtgqQSs53eiu4IJjep41+SeZX78gyzHMw/7YLnA1vyN8f7OSC20OAodl4AzaSeZnXpzLKGKkql9Kf6qi/SbZ1ACViVlG4Wh+DScaXV0OlFY4lHz+yMjN8+bhmNzcHSnW8yuBg6gQ9/TV2c6MrHukjl7NOWFDbB+CnsY3RX6ntfAA5+vCAxcP+cu6ZWkiWRkX7mkK5JF8KXK5xImMwHTeiRFofVNlqeDoWwNZ7GdcHYaln6quXZJx8cM40zhYMRWQfPgJ8zYXdtyxL7ivoWv/Msw/pDbIaempvVl+9Hv2I4+jHYtvKGRnr8DF8Y8C5jlCr7yqhcqsS/F3xzJqHdk6laUhJLR+ibL+Q2j5P2Gs7ja6kKj4k3Gw9BpRsRb8rulVU19wsWt/JGe38lgoLblCDQw6N4cnsq7fU3bHpj8Uye3+Xl+2SL7R49OXbhk7sYM6ZK+fWX/K4AmfQkpMhYwtYZWIOXsYChYw1lfc/Nn4iR3kHDw3HFdM55j3xk2rn3TjL3+oYvP2cy+v9wI9Ml4kJLUfnIEgp99Z5g9/gku0lS9/YnN9As4x+BjE9tw5rjQKE/qze5MoPirOjL1Y2ZFWCI0Cq1YynZEOooyB6Vz2U44iiwD1Eg1KC2xuy1md1KC87j1iie0Pr+9/memuud1vMt54gEMvo/FF+HjVB0/csC69kZxB9/qtacAnD14vgCLf9mNcX0lkUOHDdm9i9xSL9lXjWQq4GyKsGgmWLGU86ywiDLnhrPoW5lI1BA5mlhsgTnfd6Xng8U6LKMG3lbvm2sDbBdsVXXBdwFg6H1saLKw/+T6OJeDSsoqu4OZMOP3fqx0H+wEENm0MWEDCeXYN8cBZ2WO6BDJyWJ4mKqgS41yLpCOokANZ1GZNR+5mzi2d3EK4kqxjGx91Z+NC9/Ujvr4IRsAW8sv/Psunui+NLsjSYml8w5P1TvN7z3NwwYgsidSzJbJySm8fXOf6y2KfSn+FoUsi7BoJljh0NsJipwblrcSJ8ep4vnkNG9BloEHYJ/8Ld4Kj5+6JoA2AY0A3Oic6smXgF27dzS/8Tu7/2l3V6cZeyNfVaR9s57rJUqA7JxIVdAlS5kXSEdRmobzcZnOR+lQPGK5k1GDCjiYhwPYGEBHEM2h4Trr2ieAMzo8l6dXtrtfsnrvNp99BYAerwExmxPnhoyNNeT6m1L8LStIwCUOvbVEFKhh2Z8zCw1n0xDKqIGRFbcFFMBpF044DHcAGwTIXITF4n9H/+0IPe9874cCGgJLb8DGodk7zb+ZcfIfTa6PLPJ+nFLUKUoUgOwFTKPQaoGicBRFaThPI5aZqkVSVKmXBfMTAIJLvxD6tBav2gCEsSmDo0wDS8cLtsXXAqjAblx9UOd6ldnsNp98BL3T8PLwhmd7g6TXvFpeZP/zFevMpItD7mRtQ0rA5QK9zaKIGlaIifOn4QzunE4Onps1zXhOjz7b2C3nQibrZfdlenjpMFz56ETbZWZ834TXuel/D3exzj2P1bYcYTAKfgLOMf2JF3AsjH0Sj00Zf3M5sHz8yRRyNhIFJpt1wLHQeiTVUeYF0okopFhalgLpRNKslE6siJ5djBS7FFgMnUNTDP+2sGoNuCH0GlFRXb3ttbM9twBA46hu7PUgvpLLMS9c/aVQdfVZ09Ka8Y8czP228XtD79dgjUY7EfD3twMaP1bqMGqsOHB2eqmbvRVrJlAzHpvakwg4F/vmqcaqiPalz+W5k8tgMCXgsoPecnEoJA0XNwqnCMGx9p0pgOIOP2iq3G1bcg8YJtRjsLVeC/Rj7MMg+/ncDhkaO+9ruKLuwBG8FdabXrf3/3fv9LqzB8/n+63MZnd1xUkz9jJbXY7p293cnTCycQ/Ph33zV+FM9i1nchUwzQSrEZoVTkQJDoZiirNmiU6v+lh4w1H7CmgS0OjZdZX79NXc+p+ZNr9uH/w5WKN17RP6LbuAYws7urN4tQXaGwBMrr6ADy/gPVbv9Dr0TAOaADr8WFlZ4QEwNv2lyrXewK42sK2wBXHhmOROwIlk90vI6+IihZx1RNbkaEBKwOULOTiOMo/C8wZHvwF2HXNiH7x85MZPMBgNdlux5MJwd2WQtVTuHbe1fw8bh7QDgfp9vdzmx0YWfR1AE27Fuo/SObwGfHdB5Q2u0PVM1x/OjP598+BvBTQG0BHARqABaBDQpL1oMlDTxsDpOPgtgb0cLVUy/viS5HVdb3FPNnr7KwEZBEwhWL1QFE6khDWcsYQm6+E3wMfCOYYura3627bqb4MfBM4AqD/de571H4wv7dfsCnmn14UGqs29vX5PB++xnttjPOddZr3iJt2iT1HrMnT9DBWnJJ7/yuNNuLWm+wmr9isTuNT5xa8GN7RZdr1gG/vm8GdPBrutAMDWRW5A6P2aQM96HUaAhsjIs7g6OaZsW/JjRBb2zXdXDSWcY0SO5O4+SsAEfRaWoFQ1nFxF8fb6oNEWvAMnXDq8j52hz9Z/DYAZu01b99biKLyhULXx3LTJj5VBNDlCd7jR6cfKANqqKj4+b/on3sZ1MGj17FHj0TcwfXZB5Q21eMRm3jb7/G8sH8WTgMaxeHvj2j82v/c0t++Rs9NLQ8N1+rUvBnraIj2fxRvb6pq+DquMtS1H9N374BzD0BT8hjTHnzMi3w2tin5q0VteIcgjYArBaoeisCTK0bCMz5ZBHLxwzD71FAATXhXYy8/9ZfWI5+ZaU1+gYmW19gS/fBF7/BMgDEDAiuhAsR7Hzm2q1/r89ft6+X5r/ene0AKDde2Dn/7tXXocs7tfinuRyZ7bak/+x2T934SG6wJo82Ola/o6XY8jYt8oRlZgL2cOvwWjVtfj4DbvYPi3gdSLgDOKvwVoJ1n0M4re6bIgi/UoAROz0DtTEiVoWF4xJHFSNAQzjBM147C6sNrgxE1oqUJLVXPLMzCGhJ3LHaE73Hs2hobrnLgz8sjIQHGDu6sTe6anj1XbG58KoM3f33HuoFE7EdD1OkbxpOSL+nXdgrYJazQ6jAhoErDCzd4asa9RE3NjhS1Wvt/q5u507r+32fSszfVNyYNP5ydNpACdnIt+IhGKItd1wLHQmuCSgdYKz4cSFg3LtWI4yRLh6LLgyGrgaMoUp4QBDE2BH7RWPAbAMX1/ZIciES8PAPyg+F969BkrDmgvmgz0t43jIenXu2TY9vG37PVPc6d/4MRNs9lX3HQhdttBHwsDL5Zkc84fOld/H9xgkgng9AVcJvalD9myINegLyVgQgJ6l85HKaXhdOQUcVvNeGSqtdYX1aEefVrDpKPzTgYDkf0BZ3KqOGUrDkoH0YQ1Gnf/JgYuoF/6ZY64xjdcYx28K4A2INLnUsK+tT5YXZH/tQWd7Pfm/DgKtq8SThvQ+1p5yJmAQSG45KAonISip2FZovB8OTi2N9ZsVyzMlEYfC4P3AeMAwC2P3x9QXC7s5cFPAGfM2F1r6ptYd0mt/b3hoz0JL9Vf0/3mJN/GhX4v7Kn3YyWAyCoj0b4zpc4SxDTAihOwouyb1+dPHxKwLMhY8yRzAqZqrBKD3rFJKPqFVZYoPJ+oYn3GMM7E7Rmsax8E24plF8AWhIGfvSF+v94gmkO+Gh3vGq7YYen89pxnaRwFxjWYNjGvOPd8pdbUJ2y4qLnlGY6fGayOta8YxGNuyVcfpQPZl8gIeR0ncwIGheAShaJwEooehZFzGk4nB2POlLAGjkYAsyE1dreGSNssHvwgg9FaHGU2jzv3fGNhxwPuxVdres75z/8qTnEArFfc5Gq4vnn/MzCEQsN1k19cLuyyBLARXCOMGhj4WQHPXW6UvPNzmvGX7EtkitIFDHJwiUIOTk7RNVxwB0sR7RrtDTPO/ZaKF73b1htdb0wdbNW39E1d0Fr1Vzt/2UJhj7lGe+LT1kdhCHKND2HPtDj7y2x11X1yxN7/UyyzzGffuCxO9k0HErAsyD7ESwImMoM0nBxVazgzB8cS62O/AYD50K+ZzePCnvogmnUYCaBDj74gmo0VBxyd32Z0TmFgne7k641r/wiNxvvm2gA6ADAYbW55xj90iZu7E7ZgRMDz2xfZCpjsS2SBOgQMcnBJQw5OTjk4GMk1LK5WminF0vM7jdo3XKHrBTSJq5IAYI3GYbjDuvvRs9NL/VgpYIXYbxKArfrbdstTswl4RsCqsK+i1Auyr3zko8KJliERGUNts5JT9DUnuRRnpVOTFXmVBB3ORlVxtZItGFmVBLhC14vdsgLY6Jq+7uz0UsfB7wOac5vqg2gW0BSxr5FFS5WkfdM5pKKjNPsSCidfAqZy6JKHHJwcJWhY3ifUaUbinCdRHR3rYAAGHkZNgPuywK0H1xjNuABspu+h18Ae/0SPY9a1jwOILCbG3LW/Ma8VdzASR1jU+KtA+9KbVC7yZDRKwET2UBROSdEdnIVvMtuwITEKSzk4YlYjC8BS8aK/axV/2UKg3T76ONtidxz81uzDU679zQGyL6Eo8jUHLEIzwWUCzQqnpLgTw1nMCidpVIl0poRj54OBSANLcVbYOWbGc7WmPv6yhew7w7zHyrbY7aOPR/b3jROw1ARwdvE3T/ZVoHpFSMBykb8B3fwmYBqILhMoCqekuCPSsudgiZdImYMx06CDrXOjc8RzMzsyHPLVuLHJPvTwnI2P1APZt+TJq8VoCJqQDXrPp6RkHCyZQSXKsmKJzuwaWbCtAprsC77r3bZ+tgJLzMdZUZT4S/YlciTvAqYQXFbQOz8lRYzCMu9pmH4RcuyEbkyXSnNvb/3pXgaj4CcieyhhZrBaVsi+RHbk21+UgAmZoeHodFBLFM5pIFpyBZEYc8X9jgC+eWEtjjIYiDg4GoJjG1vGHVJum/7mTtHr24mSoRACphBchpCDU1LcKJz+nTMdiJ4X3+yGwTbTfWbsZjaPB/asYDDWbPpfDAYY/m04xzA0Fbmn6OCEnpeJ9V/JkTf+Kly99L6TkQKYq0AJmBxchlAUTge1OzgzIjs0TADwd61y79kYwEYn7hzx/Jfm9mfRpeWwHfwgvGH4WPjYWAdLNN5K49jIvkR2FMZZNARN5Be6KKSkWFFYFjmlFYJFj3rDcI6JOwTDhtqdhwX2cnCN4BoFrOD7rUKQA8BgFF4eQ1MYmuIOPQiHBX7DvHs/FBCyLyE7+V0HHActCy5naK1wSoqyVjj9JcLZbNUgilMUsF2HVSfMvb3id9y4MbLnIBDtGg1+wozn/FgpoEnffSzYYxG49eK2hrhGg5k55ljrJ0nAMsZfhdsXJGBZKdiQLSVgokDQBSIlRYnC6Vsq44Ho2NgqTuv2Tus7+pnN436sBBDZ8TfaNbqlClyj+C1b+y8CPbdbKl40fe4xhn8bqxcXKwSrouSK3lwqpbKQL6bxhSgElzPiZYKicHIqA+6i76eUETrNSLLCqGg9s5fn8PtAXzuDMQErIjv+Ron8m20OPmvX/cb+0eW2lpB99GHsGgQa4GNRC0zWCzVzW2LlOf4qX70g+8pNISuWCp2AqRqLoOtFSgqcuvISgqUCqxub3Oh0st9jMDDbcjL2BvAeK2xBtGns7O/QUgVuOYMB66m74LDAYcGfr5T3J0oC2bcMKbChCpqACUKEonA6FDIKM7wrl12E5yXaCNobBiCAAxrQUiUY1gCI33Bwsh5Wl9N6J+ADgFoAgMEgGDc4TjTZqm6xTz2F1Xbgr/IfZwJkX6IAFGEOmEIwIUKXj5QoMAenFYIn6+E3wG+IrCaaab5hM/1ydkfCxO1+a8bjb5jZWhiwTz0FqwvcYMojyTH+qmLSF/T2yQOFd1NxirDIwYQIrRVOSSF9kPvIbaQE2m/gDj0Iuw5DU/oTL8A5BucYNmjsnu9Yhe/Otrua2ypL3F14zh7DUQ2zreJ/Mowz+cKn3O2by8MJ9VIUK1EVNFF8yMEpUZQYEqNnohTd2GRd+vemzU+ju8LWfp8Zz2HfGLjljsZ/NTu3o9YXu8/gHOkmfpEbRJsGJ+aYVTL+lo996S1TGhRtDpgqoolYaFY4JYWZEpZnMrjWJyzb4Dh0Occ/5MGPAgAHK9hWcd2R+8IbRfum2D1JPB7GKQgcan3gGoHxJPGX7EtkTbEGZYtZhEUOJuLQ+ibJwUkQJZFvDcvjYAMPgDU54Jn5ipGFgY9m30T7JspVXN3EME6hBlg9Hn2I7LsvkH3LmSJOiVIVNKEsKAqnRAkLhXWh0fkaY8XCe6zWiq+6pq8D9sLLw6aRtO98uTb6dZ0uReurrOOvitQLsm/JUWQBUwgmJKEonJx8OzjTEBzfiMNhgTcM/rgTd4Kpg5F1Yr3Zud1de2PcAzPbT4nsS8hNcSuCi5+AycGEJBSFk5Pv4eicBqINPIbCevQFuC9H+k0CbtttqBlETPxN37756HhF9iWKvh6n+AImiCSQhpOjhOFoCWp9aLEETmwEEJn3BeIGn5PYN80p3lyqrsi+hBJQxDKkon8MIRQOXYCSkL+FwjnWFVsrfjz7H2nYVxcaFW/5PjayLwFleEcpCZgGoonkUBROjgKjsHd6HYyaxLLnRPtmWtVcJsPOIPvmDSXYFwpJwCIK+Y0QSoauR0nIh10yUl2kDZbYhxIIoCPxPnH2TT/yRo+H7EvkiHJcoyABE0Q6UPfKJORjODoz4UU3QRqa0qMP3vDsdoQJFEy9IPsSikRZAlbOBxNC4dDlKQlFkU20CzQcFhzywcgGlt0ALw8fG7VybPzN1L65HBvZl4iiKMsoS8BQ2G+HUDIUhZMgr3Ik/Rc16OwiYGcrd+hB0/IdWGbBhWOwutCmwdBUYghO3745Bl+QfYkYlOYXpRRhEUR2UHHWfMi7UDj5smBB4DBZj0MfO1d/HxM+LD0e+UbNONbU44NGcDFtrdKwb+77MkGF6gXZt8xQooCpIprIFOqcNR/5ro4OhpsFgYOzFXYdllnAHcfcTpNCDWAzpN/GWRb1guxLJKC0+AtlChjkYCJzKArPh1wOlgzBOs2IAA61PlwIfNAIIG7LXp1uBAv7Iv9Oal+51AuyL5GAAu0LQNPScm2xj2FeyMFEFpCD50MWDcc5OLolQ3QmeL4mG0meU0b1guxLJKBM+0KxCZggsoai8HzIEoXjcnB0W6Ts2muQekH2LWMUnYBBIZjIDdKwJLlrOMcNg+X1rgjZl5BEsfEXyk/ANBlM5AIVZ0mSexQWDZqFhkm9UUi9BUDJ9oXyBQxyMJEbNCItiSyLlDLScD7UC7IvMT8Kty9UIWCQg4mcIQ1LIqOGIWXiPElXRKXqBdm3ICjfvlCLgAlCFkjDksi4Tin3J0kTsi9RAiiuFeV8qOLjDKEK6AqYSP42FZYdFR1qInTuFQa1+EJNCZgGogm5oCgsibzdK2VHvd4FqbeAqMW+UJeAQQ4mZIU0LIkCNaxq9YLsW0BUZF+oTsAgBxNyQxqWRCEaVrt6QfYtIOqyL9QoYILIB7RiWJKo/wpvYlIvUfKoUsAUgol8QFE4CQUzcQl4V4TsW2BUF3+hUgGDHEzkDdJwcvJk4pLxrgjZt8Co0b5Qr4BBDibyCWk4JXHKzNTHJWbcKKTewqNS+0LVAgY5mMgzpOH0KVWhZgTZt/Co175Qu4BBDibyD2mYSAmptyio2r5QUSesJKj9b0CoAq1vki6yhCR0YhSFErjyqz4BE0QhoTRMxELqJXKhRARMA9FEISENE6Te4lIC8RelMQQtUhp/D0JF0KB02UJ/9+JSMlf7EknAIpSDicJDabisIPUWnZKxL0pMwCAHE0Uiel0mE5cqpF4lUEr2RekJGORgoqhQIC49SL0KocTsi1KaA46l9P5OhLqg6eGSgf6OCqEkr+olmIBFKAcTRYfSsKoh9SqHkrQvSljAIAcTyoCmh1UHqVdRlKp9UdoCBjmYUBIUiJUPqVdplLB9UfICBjmYUBgUiBUIeVeZlLZ9UQ4CBjmYUCRkYiVA6lUsJW9flImAQQ4mFAwNTRcFUq+SKQf7onwEDHIwoWwoEBcG8q7yKRP7oqwEDHIwoQbIxHmC1KsKyse+KDcBgxxMqAcysSyQd1VEWdkXZShgkIMJtRGrEJJxmpB3VUe52RflKWCQgwnVQrE4OeRdlVKG9kXZChjkYELlUCyOQtJVO+VpX5SzgEEOJkqFMpQxSbdkKFv7oswFDHIwUXLEmamUfEzSLT3K2b4gAYMcTJQ0qvYxGbe0KXP7ggQsQg4mygRJpSnEyqTbsoLsCxJwFHIwUbYkMZ/sbibLEiD7zkACnoUcTBBxkC8J2SH7Rqko9gEoCzozCIIg8gddY2MhAcdD5wdBEEQ+oKtrHCRgCegsIQiCkBe6riZCApaGzhWCIAi5oCuqJFSENS/iGUNlWQRBEFlD6k0CJeAU0NlDEASRHXT9TA4JODV0DhEEQWQKXTlTQgJOCzqTCIIg0oeumelAAk4XOp8IgiDSga6WaUICzgA6qwiCIJJD18n0oSrozKDSaIIgCElIvZlCCTgb6DwjCIKIha6KWUACzhI62wiCIEToepgdJODsoXOOIAiCroRZQ3PAOUFTwgRBlC2k3hyhBCwDdBYSBFFu0HUvd0jA8kDnIkEQ5QNd8WTh/wN3UhMlV90slwAAAABJRU5ErkJggg==",
      "text/plain": [
       "<PIL.Image.Image image mode=RGB size=640x480>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import os\n",
    "\n",
    "# Pytorch does not currently support complex numbers on MPS\n",
    "temp_device = device\n",
    "if device == \"mps\":\n",
    "    device = \"cpu\"\n",
    "\n",
    "counts = mandelbrot(\n",
    "    # render_size=(1920,1080), # HD\n",
    "    render_size=(640, 480),\n",
    "    center=(-0.5, 0),\n",
    "    zoom=4,\n",
    "    cycles=200,\n",
    ")\n",
    "\n",
    "img = render(counts)\n",
    "print(img.size)\n",
    "\n",
    "# restore device\n",
    "device = temp_device\n",
    "\n",
    "# display image\n",
    "img"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ZeWypKgGCtiR"
   },
   "source": [
    "Mandlebrot rendering programs are both simple and infinitely complex at the same time. This view shows the entire Mandlebrot universe simultaneously, as a view completely zoomed out. However, if you zoom in on any non-black portion of the plot, you will find infinite hidden complexity.\n",
    "\n",
    "## Simple PyTorch Regression: MPG\n",
    "\n",
    "This example shows how to encode the MPG dataset for regression and predict values. We will see if we can predict the miles per gallon (MPG) for a car based on the car's weight, cylinders, engine size, and other features. We will begin by reading the MPG dataset.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "id": "szrd-36UE_ZA"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "from sklearn import preprocessing\n",
    "from sklearn.metrics import accuracy_score\n",
    "from sklearn.model_selection import train_test_split\n",
    "from torch.autograd import Variable\n",
    "\n",
    "# Read the MPG dataset.\n",
    "df = pd.read_csv(\n",
    "    \"https://data.heatonresearch.com/data/t81-558/auto-mpg.csv\", na_values=[\"NA\", \"?\"]\n",
    ")\n",
    "\n",
    "cars = df[\"name\"]\n",
    "\n",
    "# Handle missing value\n",
    "df[\"horsepower\"] = df[\"horsepower\"].fillna(df[\"horsepower\"].median())\n",
    "\n",
    "# Pandas to Numpy\n",
    "x = df[\n",
    "    [\n",
    "        \"cylinders\",\n",
    "        \"displacement\",\n",
    "        \"horsepower\",\n",
    "        \"weight\",\n",
    "        \"acceleration\",\n",
    "        \"year\",\n",
    "        \"origin\",\n",
    "    ]\n",
    "].values\n",
    "y = df[\"mpg\"].values  # regression\n",
    "\n",
    "# Numpy to PyTorch\n",
    "x = torch.tensor(x, device=device, dtype=torch.float32)\n",
    "y = torch.tensor(y, device=device, dtype=torch.float32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Af0KuIwCFOMZ"
   },
   "source": [
    "We use Pandas to load the CSV file, as previously demonstrated. We will save the names of the cars, though the car names do not help predict the MPG. Horsepower does have missing values, so we substitute the median value for any missing values. Next, we convert Pandas to NumPy, and Numpy to PyTorch. We select only the fields that we wish to use to predict. As previously discussed, we designed the Net class to detect the size of this data and add the appropriate count of input neurons.\n",
    "\n",
    "You define your neural network in the **Sequence** above. We will see later that you may also construct a neural network using a Python class. In this case, we have a neural network with an input layer equal to the number of inputs you specify from the MPG dataset. The neural network connects these inputs to 50 neurons in the first hidden layer, which are connected to 25 neurons in the second layer. The output neuron count for a layer must always match the input count of the next layer.\n",
    "\n",
    "For this book, we will generally always use the Relu activation function for hidden layers. The output layer will use no transfer function for a regression neural network like this MPG example. For classification, we use the logistic for binary classification (just two classes) or log softmax for two or more classes.\n",
    "\n",
    "For the neural network to perform correctly, everything must align. The **sequence** must specify all layers with the same number of outputs as inputs for each connection. \n",
    "\n",
    "We are ready to create the neural network, loss function, and optimizer class with the data loaded.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "id": "tY8-elD1FIsk"
   },
   "outputs": [],
   "source": [
    "# Create the neural network\n",
    "model = nn.Sequential(\n",
    "    nn.Linear(x.shape[1], 50),\n",
    "    nn.ReLU(),\n",
    "    nn.Linear(50, 25),\n",
    "    nn.ReLU(),\n",
    "    nn.Linear(25, 1)\n",
    ")\n",
    "\n",
    "# PyTorch 2.0 Model Compile (improved performance)\n",
    "model = torch.compile(model,backend=\"aot_eager\").to(device)\n",
    "\n",
    "# Define the loss function for regression\n",
    "loss_fn = nn.MSELoss()\n",
    "\n",
    "# Define the optimizer\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.01)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "41vdE4UqFO-p"
   },
   "source": [
    "We create the neural network with one input equal to the number of columns in the x-input data. We specify one output neuron which will predict the MPG. Next, we define MSELoss as the error function, which is a common choice for regression. We will use the Adam optimizer with a learning rate of 0.01 to train the network. Adam is a common choice, and 0.01 is a good start for a learning rate. The learning rate should never be above 1.0. Too large of a learning rate will fail to learn the problem thoroughly, and too low of a learning rate will take a long time to train. We will see more advanced methods for choosing the learning rate, including schedules that change it throughout training.\n",
    "\n",
    "With the objects created, we can now train the neural network.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "XapHMWRrFKfJ",
    "outputId": "3520d6a7-fec5-4c82-b516-b22a2cf0be5d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0, loss: 68332.7734375\n",
      "Epoch 100, loss: 151.1114959716797\n",
      "Epoch 200, loss: 124.54296112060547\n",
      "Epoch 300, loss: 76.19166564941406\n",
      "Epoch 400, loss: 36.22512435913086\n",
      "Epoch 500, loss: 25.9182186126709\n",
      "Epoch 600, loss: 20.210752487182617\n",
      "Epoch 700, loss: 15.857571601867676\n",
      "Epoch 800, loss: 13.010799407958984\n",
      "Epoch 900, loss: 11.432814598083496\n"
     ]
    }
   ],
   "source": [
    "# Train for 1000 epochs.\n",
    "for epoch in range(1000):\n",
    "    optimizer.zero_grad()\n",
    "    out = model(x).flatten()\n",
    "    loss = loss_fn(out, y)\n",
    "    loss.backward()\n",
    "    optimizer.step()\n",
    "\n",
    "    # Display status every 100 epochs.\n",
    "    if epoch % 100 == 0:\n",
    "        print(f\"Epoch {epoch}, loss: {loss.item()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "fqUK73GtE5jU"
   },
   "source": [
    "We now loop over 1,000 epochs and train the neural network; we define an epoch as one complete pass over the training set. We zero the gradients, so training from the previous epoch does not influence the current epoch. We present the entire training set to the model as one large batch. Later we will see more advanced ways to segment the data. We apply the loss function and use backpropagation to calculate the gradients to update the neural network weights.\n",
    "\n",
    "## Introduction to Neural Network Hyperparameters\n",
    "\n",
    "This network includes several hidden layers, with 50 and 25 neurons each. You might be wondering how the programmer chose these numbers. Selecting a hidden neuron structure is one of the most common questions about neural networks. Unfortunately, there is no right answer. These are hyperparameters. They are settings that can affect neural network performance, yet there are no clearly defined means of setting them.\n",
    "\n",
    "In general, more hidden neurons mean more capability to fit complex problems. However, too many neurons can lead to overfitting and lengthy training times. Too few can lead to underfitting the problem and will sacrifice accuracy. Also, how many layers you have is another hyperparameter. In general, more layers allow the neural network to perform more of its feature engineering and data preprocessing. But this also comes at the expense of training times and the risk of overfitting. In general, you will see that neuron counts start larger near the input layer and tend to shrink towards the output layer in a triangular fashion.\n",
    "\n",
    "Some techniques use machine learning to optimize these values. These will be discussed in [Module 8.3](t81_558_class_08_3_pytorch_hyperparameters.ipynb).\n",
    "\n",
    "## Regression Prediction\n",
    "\n",
    "Next, we will perform actual predictions. The program assigns these predictions to the **pred** variable. These are all MPG predictions from the neural network. Notice that this is a 2D array? You can always see the dimensions of what PyTorch returns by printing out **pred.shape**. Neural networks can return multiple values, so the result is always an array. Here the neural network only returns one value per prediction (there are 398 cars, so 398 predictions). However, a 2D range is needed because the neural network has the potential of returning more than one value.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "HbErLyX_CtiR",
    "outputId": "25921c8c-6b2f-4cb9-dd5e-b7dcca494c73"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape: torch.Size([398, 1])\n",
      "tensor([[15.5688],\n",
      "        [14.7666],\n",
      "        [15.8473],\n",
      "        [16.2437],\n",
      "        [15.6144],\n",
      "        [11.2223],\n",
      "        [11.0434],\n",
      "        [11.1329],\n",
      "        [11.0551],\n",
      "        [13.2947]], device='mps:0', grad_fn=<SliceBackward0>)\n"
     ]
    }
   ],
   "source": [
    "pred = model(x)\n",
    "print(f\"Shape: {pred.shape}\")\n",
    "print(pred[0:10])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "jk_lWnScCtiR"
   },
   "source": [
    "We would like to see how good these predictions are. We know the correct MPG for each car so we can measure how close the neural network was. We will first see how we calculate RMSE with standard Sklearn metrics. To utilize Sklearn we must bring the predictions back to the CPU and deatch them from the neural network graph. The following code accomplishes this with **cpu().detach()**.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "vrY0Vs9oCtiR",
    "outputId": "f6d41314-5cd7-4209-f6ec-c17235414593"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Final score (RMSE): 3.264413356781006\n"
     ]
    }
   ],
   "source": [
    "from sklearn import metrics\n",
    "\n",
    "# Measure RMSE error.  RMSE is common for regression.\n",
    "score = np.sqrt(metrics.mean_squared_error(pred.cpu().detach(), y.cpu().detach()))\n",
    "print(f\"Final score (RMSE): {score}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "WkLxHFUmMcD7"
   },
   "source": [
    "We can accomplish the same task entirely within PyTorch with less code. It is important to know how to perform these calculations both with PyTorch and Scikit-learn.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "atZ8m2bGMnz9",
    "outputId": "66e2e5b2-a226-4ff2-c14c-1a24619c3759"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Final score (RMSE): 3.264413595199585\n"
     ]
    }
   ],
   "source": [
    "score = torch.sqrt(torch.nn.functional.mse_loss(pred.flatten(), y))\n",
    "print(f\"Final score (RMSE): {score}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "i3yqZwhnCtiS"
   },
   "source": [
    "The number printed above is the average number of predictions above or below the expected output. We can also print out the first ten cars with predictions and actual MPG.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Tl7hv8NnCtiS",
    "outputId": "a0d37235-031b-4a74-a122-1a4fd08b5c0b"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1. Car name: chevrolet chevelle malibu, MPG: 18.0, predicted MPG: tensor([15.5688], device='mps:0', grad_fn=<SelectBackward0>)\n",
      "2. Car name: buick skylark 320, MPG: 15.0, predicted MPG: tensor([14.7666], device='mps:0', grad_fn=<SelectBackward0>)\n",
      "3. Car name: plymouth satellite, MPG: 18.0, predicted MPG: tensor([15.8473], device='mps:0', grad_fn=<SelectBackward0>)\n",
      "4. Car name: amc rebel sst, MPG: 16.0, predicted MPG: tensor([16.2437], device='mps:0', grad_fn=<SelectBackward0>)\n",
      "5. Car name: ford torino, MPG: 17.0, predicted MPG: tensor([15.6144], device='mps:0', grad_fn=<SelectBackward0>)\n",
      "6. Car name: ford galaxie 500, MPG: 15.0, predicted MPG: tensor([11.2223], device='mps:0', grad_fn=<SelectBackward0>)\n",
      "7. Car name: chevrolet impala, MPG: 14.0, predicted MPG: tensor([11.0434], device='mps:0', grad_fn=<SelectBackward0>)\n",
      "8. Car name: plymouth fury iii, MPG: 14.0, predicted MPG: tensor([11.1329], device='mps:0', grad_fn=<SelectBackward0>)\n",
      "9. Car name: pontiac catalina, MPG: 14.0, predicted MPG: tensor([11.0551], device='mps:0', grad_fn=<SelectBackward0>)\n",
      "10. Car name: amc ambassador dpl, MPG: 15.0, predicted MPG: tensor([13.2947], device='mps:0', grad_fn=<SelectBackward0>)\n"
     ]
    }
   ],
   "source": [
    "# Sample predictions\n",
    "for i in range(10):\n",
    "    print(f\"{i+1}. Car name: {cars[i]}, MPG: {y[i]}, \" + f\"predicted MPG: {pred[i]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "U_dpgVtfCtiS"
   },
   "source": [
    "## Simple TensorFlow Classification: Iris\n",
    "\n",
    "Classification is how a neural network attempts to classify the input into one or more classes. The simplest way of evaluating a classification network is to track the percentage of training set items classified incorrectly. We typically score human results in this manner. For example, you might have taken multiple-choice exams in school in which you had to shade in a bubble for choices A, B, C, or D. If you chose the wrong letter on a 10-question exam, you would earn a 90%. In the same way, we can grade computers; however, most classification algorithms do not merely choose A, B, C, or D. Computers typically report a classification as their percent confidence in each class. Figure 3.EXAM shows how a computer and a human might respond to question number 1 on an exam.\n",
    "\n",
    "**Figure 3.EXAM: Classification Neural Network Output**\n",
    "![Classification Neural Network Output](https://data.heatonresearch.com/images/wustl/class/class-multi-choice.png \"Classification Neural Network Output\")\n",
    "\n",
    "As you can see, the human test taker marked the first question as \"B.\" However, the computer test taker had an 80% (0.8) confidence in \"B\" and was also somewhat sure with 10% (0.1) on \"A.\" The computer then distributed the remaining points to the other two. In the simplest sense, the machine would get 80% of the score for this question if the correct answer were \"B.\" The computer would get only 5% (0.05) of the points if the correct answer were \"D.\"\n",
    "\n",
    "We previously saw how to train a neural network to predict the MPG of a card. Based on four measurements, we will now see how to predict a class, such as the type of iris flower. The code to classify iris flowers is similar to MPG; however, there are several important differences:\n",
    "\n",
    "- The output neuron count matches the number of classes (in the case of Iris, 3).\n",
    "- The **LogSoftmax** transfer function is utilized by the output layer.\n",
    "- The loss function is **CrossEntropyLoss**.\n",
    "- We call the **train** function to inform PyTorch that we are now in training mode.\n",
    "- Later, we call the **eval** function to inform PyTorch that we are done training and are evaluating the network.\n",
    "\n",
    "Softmax is commonly used in classification tasks because it allows us to transform the output of a model into a probability distribution over multiple classes. By applying the softmax function to a model's logits (raw outputs), we obtain normalized probabilities that sum up to one, indicating the likelihood of each class. This technique lets us interpret the model's predictions as confidence scores for different classes and make decisions based on the highest probability.\n",
    "\n",
    "Log softmax, on the other hand, offers several advantages over regular softmax. One advantage is that it helps to alleviate numerical instability issues that can occur when dealing with small or large values in softmax computations. By taking the logarithm of the softmax probabilities, we work in the logarithmic domain, which can improve numerical stability and prevent overflow or underflow errors. Log softmax is also useful when applying the negative log-likelihood loss function, as it simplifies the mathematical calculations by eliminating the need to compute the actual probabilities. This technique is particularly beneficial when training neural networks as it simplifies optimization. Log softmax provides a practical and efficient way to handle classification problems, offering numerical stability and simplifying loss computation.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "aLp65T9JCtiS",
    "outputId": "28975453-2c5f-43fc-eabd-2b8b5f522d47"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0, loss: 1.1010128259658813\n",
      "Epoch 100, loss: 0.04871285706758499\n",
      "Epoch 200, loss: 0.04354476183652878\n",
      "Epoch 300, loss: 0.04309936985373497\n",
      "Epoch 400, loss: 0.04026123508810997\n",
      "Epoch 500, loss: 0.04329764470458031\n",
      "Epoch 600, loss: 0.039747048169374466\n",
      "Epoch 700, loss: 0.03971880301833153\n",
      "Epoch 800, loss: 0.049878157675266266\n",
      "Epoch 900, loss: 0.03968413919210434\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "from sklearn import preprocessing\n",
    "\n",
    "df = pd.read_csv(\n",
    "    \"https://data.heatonresearch.com/data/t81-558/iris.csv\", na_values=[\"NA\", \"?\"]\n",
    ")\n",
    "\n",
    "le = preprocessing.LabelEncoder()\n",
    "\n",
    "x = df[[\"sepal_l\", \"sepal_w\", \"petal_l\", \"petal_w\"]].values\n",
    "y = le.fit_transform(df[\"species\"])\n",
    "species = le.classes_\n",
    "\n",
    "x = torch.tensor(x, device=device, dtype=torch.float32)\n",
    "y = torch.tensor(y, device=device, dtype=torch.long)\n",
    "\n",
    "model = nn.Sequential(\n",
    "    nn.Linear(x.shape[1], 50),\n",
    "    nn.ReLU(),\n",
    "    nn.Linear(50, 25),\n",
    "    nn.ReLU(),\n",
    "    nn.Linear(25, len(species)),\n",
    "    nn.LogSoftmax(dim=1),\n",
    ")\n",
    "\n",
    "# PyTorch 2.0 Model Compile (improved performance)\n",
    "model = torch.compile(model,backend=\"aot_eager\").to(device)\n",
    "\n",
    "criterion = nn.CrossEntropyLoss()  # cross entropy loss\n",
    "\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.01)\n",
    "\n",
    "model.train()\n",
    "for epoch in range(1000):\n",
    "    optimizer.zero_grad()\n",
    "    out = model(x)\n",
    "    # Note: CrossEntropyLoss combines nn.LogSoftmax() and nn.NLLLoss() so don't use Softmax in the model\n",
    "    loss = criterion(out, y)\n",
    "    loss.backward()\n",
    "    optimizer.step()\n",
    "\n",
    "    if epoch % 100 == 0:\n",
    "        print(f\"Epoch {epoch}, loss: {loss.item()}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "QPM30WdBCtiS",
    "outputId": "185a1859-a5dc-4251-a947-80a4246cc594"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Iris-setosa' 'Iris-versicolor' 'Iris-virginica']\n"
     ]
    }
   ],
   "source": [
    "# Print out number of species found:\n",
    "\n",
    "print(species)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "dM8-xyDxCtiS"
   },
   "source": [
    "Now that you have a neural network trained, we would like to be able to use it. The following code makes use of our neural network. Exactly like before, we will generate predictions. Notice that three values come back for each of the 150 iris flowers. There were three types of iris (Iris-setosa, Iris-versicolor, and Iris-virginica). We call the **eval** function to inform PyTorch that we are no longer training and wish to evaluate.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "3YzlVpw-CtiS",
    "outputId": "2edf87f0-b57a-4f49-b7fc-78ac1c802a19"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape: torch.Size([150, 3])\n",
      "tensor([[-1.5497e-06, -1.3389e+01, -3.7496e+01],\n",
      "        [-9.4175e-06, -1.1576e+01, -3.4396e+01],\n",
      "        [-3.2186e-06, -1.2651e+01, -3.5624e+01],\n",
      "        [-1.1563e-05, -1.1369e+01, -3.3883e+01],\n",
      "        [-1.1921e-06, -1.3680e+01, -3.7835e+01],\n",
      "        [-3.5763e-06, -1.2532e+01, -3.6687e+01],\n",
      "        [-4.0531e-06, -1.2421e+01, -3.5146e+01],\n",
      "        [-3.4571e-06, -1.2563e+01, -3.6270e+01],\n",
      "        [-1.5616e-05, -1.1066e+01, -3.2946e+01],\n",
      "        [-6.7949e-06, -1.1893e+01, -3.5297e+01]], device='mps:0',\n",
      "       grad_fn=<SliceBackward0>)\n"
     ]
    }
   ],
   "source": [
    "model.eval()\n",
    "pred = model(x)\n",
    "print(f\"Shape: {pred.shape}\")\n",
    "print(pred[0:10])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "JCay-JrvCtiS"
   },
   "source": [
    "If you would like to turn of scientific notation, the following line can be used:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "id": "nXZne7ZICtiS"
   },
   "outputs": [],
   "source": [
    "np.set_printoptions(suppress=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "nOvMqI7QCtiS"
   },
   "source": [
    "Now we see these values rounded up.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "F9PSDexjCtiS",
    "outputId": "df299de1-6686-44cb-a894-9b20639c0d10"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ -0.00000155 -13.388674   -37.495525  ]\n",
      " [ -0.00000942 -11.576251   -34.39628   ]\n",
      " [ -0.00000322 -12.65121    -35.62356   ]\n",
      " [ -0.00001156 -11.368952   -33.883083  ]\n",
      " [ -0.00000119 -13.679908   -37.835045  ]\n",
      " [ -0.00000358 -12.53195    -36.686802  ]\n",
      " [ -0.00000405 -12.420665   -35.14558   ]\n",
      " [ -0.00000346 -12.563189   -36.26971   ]\n",
      " [ -0.00001562 -11.066368   -32.945507  ]\n",
      " [ -0.00000679 -11.893266   -35.296593  ]]\n"
     ]
    }
   ],
   "source": [
    "print(pred[0:10].cpu().detach().numpy())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "qDC7hxqECtiS"
   },
   "source": [
    "Usually, the program considers the column with the highest prediction to be the prediction of the neural network. It is easy to convert the predictions to the expected iris species. The argmax function finds the index of the maximum prediction for each row.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "367mbx_PCtiT",
    "outputId": "c022460c-ec3e-491d-85a8-f9b9f0f73776"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predictions: tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
      "        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
      "        0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n",
      "        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n",
      "        1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n",
      "        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n",
      "        2, 2, 2, 2, 2, 2], device='mps:0')\n",
      "Expected: tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
      "        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
      "        0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n",
      "        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n",
      "        1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n",
      "        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\n",
      "        2, 2, 2, 2, 2, 2], device='mps:0')\n"
     ]
    }
   ],
   "source": [
    "_, predict_classes = torch.max(pred, 1)\n",
    "print(f\"Predictions: {predict_classes}\")\n",
    "print(f\"Expected: {y}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "lrcy0Q4xCtiT"
   },
   "source": [
    "Of course, it is straightforward to turn these indexes back into iris species. We use the species list that we created earlier.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "NTORTvygCtiT",
    "outputId": "7be0ba26-172c-4b5d-9353-7b6b349cc879"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Iris-setosa' 'Iris-setosa' 'Iris-setosa' 'Iris-setosa' 'Iris-setosa'\n",
      " 'Iris-setosa' 'Iris-setosa' 'Iris-setosa' 'Iris-setosa']\n"
     ]
    }
   ],
   "source": [
    "print(species[predict_classes[1:10].cpu().detach()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Ljez1ZRACtiT"
   },
   "source": [
    "Accuracy might be a more easily understood error metric. It is essentially a test score. For all of the iris predictions, what percent were correct? The downside is it does not consider how confident the neural network was in each prediction.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "zth2S2OcCtiT",
    "outputId": "64510162-96ed-45f7-b72c-8c22e2aa127a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 0.9866666666666667\n"
     ]
    }
   ],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "correct = accuracy_score(y.cpu().detach(), predict_classes.cpu().detach())\n",
    "print(f\"Accuracy: {correct}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "jY07aiLICtiT"
   },
   "source": [
    "The code below performs two ad hoc predictions. The first prediction is a single iris flower, and the second predicts two iris flowers. Notice that the **argmax** in the second prediction requires **axis=1**? Since we have a 2D array now, we must specify which axis to take the **argmax** over. The value **axis=1** specifies we want the max column index for each row.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "FZUSWVGnCtiT",
    "outputId": "e7e4be75-b618-4f1a-b7f3-9cdcafcd9a14"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-10.3541,  -0.3847,  -1.1415]], device='mps:0',\n",
      "       grad_fn=<CompiledFunctionBackward>)\n",
      "Predict that tensor([[5., 3., 4., 2.]], device='mps:0') is: Iris-versicolor\n"
     ]
    }
   ],
   "source": [
    "sample_flower = torch.tensor([[5.0, 3.0, 4.0, 2.0]], device=device)\n",
    "pred = model(sample_flower)\n",
    "print(pred)\n",
    "_, predict_classes = torch.max(pred, 1)\n",
    "print(f\"Predict that {sample_flower} is: {species[predict_classes]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ENSZaaRICtiT"
   },
   "source": [
    "You can also predict two sample flowers.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "fdSSkkmwCtiT",
    "outputId": "69bcfb15-5964-46d0-b47a-7d8114d3ee95"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-1.0354e+01, -3.8473e-01, -1.1415e+00],\n",
      "        [-5.2451e-05, -9.8563e+00, -3.1152e+01]], device='mps:0',\n",
      "       grad_fn=<CompiledFunctionBackward>)\n",
      "Predict that these two flowers tensor([[5.0000, 3.0000, 4.0000, 2.0000],\n",
      "        [5.2000, 3.5000, 1.5000, 0.8000]], device='mps:0') \n",
      "are: ['Iris-versicolor' 'Iris-setosa']\n"
     ]
    }
   ],
   "source": [
    "sample_flower = torch.tensor(\n",
    "    [[5.0, 3.0, 4.0, 2.0], [5.2, 3.5, 1.5, 0.8]], device=device\n",
    ")\n",
    "pred = model(sample_flower).to(device)\n",
    "print(pred)\n",
    "_, predict_classes = torch.max(pred, 1)\n",
    "print(f\"Predict that these two flowers {sample_flower} \")\n",
    "print(f\"are: {species[predict_classes.cpu().detach()]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You might notice that the output is negative, rather than probabilities that add to 1.0. You can see that each result is 3 numbers, ideally these would be the probability that the input was each of the three type of iris flower. This is due to the log softmax function we used. To convert these values to traditional probabilities just use torch.exp."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[3.1863e-05, 6.8064e-01, 3.1933e-01],\n",
      "        [9.9995e-01, 5.2418e-05, 2.9569e-14]], device='mps:0',\n",
      "       grad_fn=<ExpBackward0>)\n"
     ]
    }
   ],
   "source": [
    "print(torch.exp(pred))"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "anaconda-cloud": {},
  "colab": {
   "collapsed_sections": [],
   "provenance": []
  },
  "gpuClass": "standard",
  "kernelspec": {
   "display_name": "Python 3.9 (torch)",
   "language": "python",
   "name": "pytorch"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.16"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
